微服务架构下统⼀认证思路主要有两种形式:
1、基于 的认证⽅式在分布式的环境下,基于 的认证会出现⼀个问题,每个应⽤服务都需要在中存储⽤户身份信息,通过负载均衡将本地的请求分配到另⼀个应⽤服务需要将 信息带过去,否则会重新认证。我们可以使⽤ 共享、 黏贴等⽅案。 ⽅案也有缺点,⽐如基于 ,移动端不能有效使⽤等
2、基于 token 的认证⽅式。基于token的认证⽅式,服务端不⽤存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地⽅,并且可以实现 web 和 app 统⼀认证机制。其缺点也很明显,token 由于⾃包含信息,因此⼀般数据量较⼤,⽽且每次请求 都需要传递,因此⽐较占带宽。另外,token 的签名验签操作也会给 cpu 带来额外的处理负担。
下面我们就基于 token 的认证⽅式。采用 框架来实现。
开放授权协议/标准
OAuth(开放授权)是⼀个开放协议/标准,允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。
协议流程图如下:
image-
1、客户端请求用户授权
2、用户确认授权
3、客户端收到授权许可后,向认证服务器申请令牌
4、认证服务器验证授权许可,向客户端返回有效令牌
5、客户端携带有效令牌访问资源服务器
6、资源服务器从认证服务器中验证有效令牌。
7、验证通过后,返回对应的资源给客户端。
什么情况下需要使⽤ ?
第三⽅授权登录的场景:⽐如imToken官网下载,我们经常登录⼀些⽹站或者应⽤的时候,可以选择使⽤第三⽅授权登录的⽅式,⽐如:微信授权登录、QQ授权登录、微博授权登录等,这是典型的 使⽤场景。单点登录的场景:如果项⽬中有很多微服务或者公司内部有很多服务,可以专⻔做⼀个认证中⼼(充当认证平台⻆⾊),所有的服务都要到这个认证中⼼做认证,只做⼀次登录,就可以在多个授权范围内的服务中⾃由串⾏。
Cloud + JWT 实现
Cloud 是 Cloud 体系对协议的实现token 权限管理·(中国)官方网站,可以⽤来做多个微服务的统⼀认证(验证身份合法性)授权(验证权限)。通过向服务(统⼀认证授权服务)发送某个类型的 进⾏集中认证和授权,从⽽获得 (访问令牌),⽽这个 token 是受其他微服务信任的。
使⽤ 解决问题的本质是,引⼊了⼀个认证授权层,认证授权层连接了资源的拥有者,在授权层⾥⾯,资源的拥有者可以给第三⽅应⽤授权去访问我们的某些受保护资源。
搭建认证服务器
创建一个新的的模块,-oauth-hw-9900。
依赖
pom 文件中依赖如下:
org.springframework.cloudspring-cloud-starter-oauth2
org.springframework.security.oauth.bootspring-security-oauth2-autoconfigure2.1.11.RELEASE
org.springframework.security.oauthspring-security-oauth22.3.4.RELEASE
org.springframework.bootspring-boot-starter-web
org.springframework.cloudspring-cloud-starter-config
org.springframework.cloudspring-cloud-starter-netflix-eureka-client
配置文件
server:
port: 9900
spring:
application:
name: service-oauth-hw
zipkin:
base-url: http://127.0.0.1:8771 # zipkin server的请求地址
sender:
# web 客户端将踪迹日志数据通过网络请求的方式传送到服务端,另外还有配置
# kafka/rabbit 客户端将踪迹日志数据传递到mq进行中转
type: web
sleuth:
sampler:
# 采样率 1 代表100%全部采集 ,默认0.1 代表10% 的请求踪迹数据会被采集
# 生产环境下,请求量非常大,没有必要所有请求的踪迹数据都采集分析,对于网络包括server端压力都是比较大的,可以配置采样率采集一定比例的请求的踪迹数据进行分析即可
probability: 1
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone: http://quellanan.a:8761/eureka/,http://quellanan.b:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
instance:
prefer-ip-address: true #使用ip注册
#分布式链路追踪
logging:
level:
org.springframework.web.servlet.DispatcherServlet: debug
org.springframework.cloud.sleuth: debug
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceOauthHw9900Application {
public static void main(String[] args) {
SpringApplication.run(ServiceOauthHw9900Application.class, args);
}
}
自定义一个 。当前类为 的配置类(需要继承特定的父类 )
@Configuration
@EnableAuthorizationServer //开启认证服务器功能
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 客户端详情配置,
* 比如client_id,secret
* 当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网
* 颁发client_id等必要参数,表明客户端是谁
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
// 客户端信息存储在什么地方,可以在内存中,可以在数据库里
clients.inMemory()
// 添加一个client配置,指定其client_id
.withClient("quellanan")
//指定客户端的密码/安全码
.secret("abcdefg")
//指定客户端所能访问资源id清单,此处的资源id是需要在具体的资源服务器上也配置一样
.redirectUris("*")
//认证类型/令牌颁发模式,可以配置多个在这里,但是不一定都用,具体使用哪种方式颁发token,需要客户端调用的时候传递参数指定
.authorizedGrantTypes("password","refresh_token")
//客户端的权限范围,此处配置为all全部即可
.scopes("all");
}
/**
* 认证服务器最终是以api接口的方式对外提供服务(校验合法性并生成令牌、校验令牌等)
* 那么,以api接口方式对外的话,就涉及到接口的访问权限,我们需要在这里进行必要的配置
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
// 相当于打开endpoints 访问接口的开关,这样的话后期我们能够访问该接口
security
// 允许客户端表单认证
.allowFormAuthenticationForClients()
// 开启端口/oauth/token_key的访问权限(允许)
.tokenKeyAccess("permitAll()")
// 开启端口/oauth/check_token的访问权限(允许)
.checkTokenAccess("permitAll()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints
// 指定token的存储方法
.tokenStore(tokenStore())
// token服务的一个描述,可以认为是token生成细节的描述,比如有效时间多少等
.tokenServices(authorizationServerTokenServices())
// 指定认证管理器,随后注入一个到当前类使用即可
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
}
/*
该方法用于创建tokenStore对象(令牌存储对象)
token以什么形式存储
*/
public TokenStore tokenStore(){
return new InMemoryTokenStore();
// 使用jwt令牌
//return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使用默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
// 针对jwt令牌的添加
//defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
// 设置令牌有效时间(一般设置为2个小时)
defaultTokenServices.setAccessTokenValiditySeconds(20); // access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
关于三个 ⽅法
关于
然后再自定义有一个配置类,主要处理用户名和密码的校验等事宜。
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
//@Autowired
//private JdbcUserDetailsService jdbcUserDetailsService;
/**
* 注册一个认证管理器对象到容器
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 密码编码对象(密码不进行加密处理)
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/**
* 处理用户名和密码验证事宜
* 1)客户端传递username和password参数到认证服务器
* 2)一般来说,username和password会存储在数据库中的用户表中
* 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在这个方法中就可以去关联数据库了,当前我们先把用户信息配置在内存中
// 实例化一个用户对象(相当于数据表中的一条用户记录)
UserDetails user = new User("admin","123456",new ArrayList<>());
auth.inMemoryAuthentication()
.withUser(user).passwordEncoder(passwordEncoder);
//auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
}
}
JWT 改造统⼀认证授权中⼼的令牌存储机制 JWT 令牌介绍
通过上边的测试我们发现,当资源服务和授权服务不在⼀起时资源服务使⽤ 远程请求授权 服务验证token,如果访问量较⼤将会影响系统的性能。
解决上边问题:令牌采⽤JWT格式即可解决上边的问题,⽤户认证通过会得到⼀个JWT令牌,JWT令牌中已经包括了⽤户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法⾃⾏完成令牌校验,⽆需每次都请求认证 服务完成授权。
什么是JWT?
JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519),它定义了⼀种简介的、⾃包含的协议格式,⽤于 在通信双⽅传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使⽤HMAC算法或使⽤RSA的公 钥/私钥对来签名,防⽌被篡改。
JWT令牌结构
JWT 令牌由三部分组成,每部分中间使⽤点(.)分隔,⽐如:xxxxx.yyyyy.zzzzz
将上边的内容使⽤编码,得到⼀个字符串就是JWT令牌的第⼀部分。
认证服务器端JWT改造(改造主配置类)
/*
该方法用于创建tokenStore对象(令牌存储对象)
token以什么形式存储
*/
public TokenStore tokenStore(){
//return new InMemoryTokenStore();
// 使用jwt令牌
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致
jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
return jwtAccessTokenConverter;
}
修改 JWT 令牌服务⽅法
/**
* 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使用默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
// 针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
// 设置令牌有效时间(一般设置为2个小时)
defaultTokenServices.setAccessTokenValiditySeconds(20); // access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
总结
我们在实际工作中,token 鉴权的方式是很常见的现在,这一套解决方案也可以直接使用到项目中,小伙伴们赶紧学习起来吧。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。