微服务架构下统⼀认证思路
基于Session的认证⽅式
在分布式的环境下,基于session的认证会出现⼀个问题,每个应⽤服务都需要在session中存储⽤
户身份信息,通过负载均衡将本地的请求分配到另⼀个应⽤服务需要将session信息带过去,否则
会重新认证。我们可以使⽤Session共享、 Session黏贴等⽅案。
Session⽅案也有缺点,⽐如基于cookie,移动端不能有效使⽤等
基于token的认证⽅式
基于token的认证⽅式,服务端不⽤存储认证数据,易维护扩展性强, 客户端可以把token 存在任
意地⽅,并且可以实现web和app统⼀认证机制。其缺点也很明显, token由于⾃包含信息,因此
⼀般数据量较⼤,⽽且每次请求 都需要传递,因此⽐较占带宽。另外, token的签名验签操作也会
给cpu带来额外的处理负担
OAuth2开放授权协议/标准
OAuth(开放授权)是⼀个开放协议/标准,允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者
上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。
允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,⽽不需要将⽤户名和密码提供给
第三⽅应⽤或分享他们数据的所有内容
OAuth2是OAuth协议的延续版本,但不向后兼容OAuth1即完全废⽌了OAuth1
OAuth2协议⻆⾊和流程
资源所有者(Resource Owner):可以理解为⽤户⾃⼰
客户端(Client):我们想登陆的⽹站或应⽤,
认证服务器(Authorization Server):可以理解为微信或者QQ
资源服务器(Resource Server):可以理解为微信或者QQ
什么情况下需要使⽤OAuth2?
第三⽅授权登录的场景:⽐如,我们经常登录⼀些⽹站或者应⽤的时候,可以选择使⽤第三⽅授权登录
的⽅式,⽐如:微信授权登录、 QQ授权登录、微博授权登录等,这是典型的 OAuth2 使⽤场景。
单点登录的场景:如果项⽬中有很多微服务或者公司内部有很多服务,可以专⻔做⼀个认证中⼼(充当
认证平台⻆⾊),所有的服务都要到这个认证中⼼做认证,只做⼀次登录,就可以在多个授权范围内的
服务中⾃由串⾏
OAuth2的颁发Token授权⽅式
1)授权码(authorization-code)
2)密码式(password)提供⽤户名+密码换取token令牌
3)隐藏式(implicit)
4)客户端凭证(client credentials)
授权码模式使⽤到了回调地址,是最复杂的授权⽅式,微博、微信、 QQ等第三⽅登录就是这种模式。
⼝对接中常使⽤的password密码模式(提供⽤户名+密码换取token)。
Spring Cloud OAuth2 + JWT 实现
Spring Cloud OAuth2介绍
Spring Cloud OAuth2 是 Spring Cloud 体系对OAuth2协议的实现,可以⽤来做多个微服务的统⼀认证
(验证身份合法性)授权(验证权限)。通过向OAuth2服务(统⼀认证授权服务)发送某个类型的
grant_type进⾏集中认证和授权,从⽽获得access_token(访问令牌),⽽这个token是受其他微服务
信任的。
注意:使⽤OAuth2解决问题的本质是,引⼊了⼀个认证授权层,认证授权层连接了资源的拥有者,在
授权层⾥⾯,资源的拥有者可以给第三⽅应⽤授权去访问我们的某些受保护资源
Spring Cloud OAuth2构建微服务统⼀认证服务思路
在我们统⼀认证的场景中, Resource Server其实就是我们的各种受保护的微服务,微服务中的
各种API访问接⼝就是资源,发起http请求的浏览器就是Client客户端(对应为第三⽅应⽤)
搭建认证服务器(Authorization Server)
认证服务器(Authorization Server),负责颁发token
新建项目
edu-cloud-oauth-server-9999
- pom
<dependencyManagement>
<dependencies>
<!--spring cloud依赖管理,引入了Spring Cloud的版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导⼊Eureka Client依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--导⼊spring cloud oauth2依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
</dependencies>
- application.yml
server:
port: 9999
spring:
application:
name: edu-cloud-oauth-server
# 注册到eureka注册中
eureka:
client:
service-url:
# 使用逗号分隔 多个实例,
defaultZone: http://EduCloudEurekaServerB:8762/eureka,http://EduCloudEurekaServerA:8761/eureka
instance:
prefer-ip-address: true #服务实例中显示ip,而不是显示主机名(兼容老的eureka版本)
# 实例名称: 192.168.1.103:edu-service-resume:8080,我们可以自定义它
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
# 自定义元数据
# metadata-map:
# cluster: cl1
# region: rn1
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
- 启动类
@EnableDiscoveryClient
@SpringBootApplication
public class OauthServerApplication9999 {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication9999.class,args);
}
}
AuthorizationServerConfigurerAdapter
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
关于三个configure⽅法
- configure(ClientDetailsServiceConfigurer clients)⽤来配置客户端详情服务(ClientDetailsService)客户端详情信息在 这⾥进⾏初始化,你能够把客户端详情信息写死在这⾥或者是通过数据库来存储调取详情信息
- configure(AuthorizationServerEndpointsConfigurer endpoints)⽤来配置令牌(token)的访问端点和令牌服务(token services)
- configure(AuthorizationServerSecurityConfigurer oauthServer)⽤来配置令牌端点的安全约束.
关于 TokenStore
InMemoryTokenStore
默认采⽤,它可以完美的⼯作在单服务器上(即访问并发量 压⼒不⼤的情况下,并且它
在失败的时候不会进⾏备份),⼤多数的项⽬都可以使⽤这个版本的实现来进⾏ 尝试,
你可以在开发的时候使⽤它来进⾏管理,因为不会被保存到磁盘中,所以更易于调试
JdbcTokenStore
这是⼀个基于JDBC的实现版本,令牌会被保存进关系型数据库。使⽤这个版本的实现
时, 你可以在不同的服务器之间共享令牌信息,使⽤这个版本的时候请注意把"springjdbc"这个依赖加⼊到你的 classpath当中。
JwtTokenStore
JwtTokenStore 这个版本的全称是 JSON Web Token(JWT),它可以把令牌相关的数
据进⾏编码(因此对于后端服务来说,它不需要进⾏存储,这将是⼀个重⼤优势),缺
点就是这个令牌占⽤的空间会⽐较⼤,如果你加⼊了⽐较多⽤户凭证信息,
JwtTokenStore 不会保存任何数据。
- Oath配置类
@Configuration
//启动认证服务
@EnableAuthorizationServer
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
* 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配
* 置
* private String tokenKeyAccess = "denyAll()";
*
* private String checkTokenAccess = "denyAll()";
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
//允许客户端表单提交
security.allowFormAuthenticationForClients()
// 开启端⼝/oauth/token_key的访问权限(允许)
.tokenKeyAccess("permitAll()")
开启端⼝/oauth/check_token的访问权限(允许)
.checkTokenAccess("permitAll()");
}
/**
* 客户端详情配置,
* ⽐如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() // 客户端信息存储在什么地⽅,可以在内存中,可以在数据库⾥
.withClient("client-edu") //添加⼀个client配置,指定其client_id
.secret("abcxyz") // 指定客户端的密码/安全码
// 指定客户端所能访问资源 id清单,此处的资源id是需要在具体的资源服务器上也配置⼀样
.resourceIds("autodeliver")
.authorizedGrantTypes("password", "refresh_token") // 认证类型/令牌颁发模式,可以配置多个在这⾥,但是不⼀定都⽤,具体使哪种⽅式颁发token,需要客户端调⽤的时候传递参数指定
//客户端权限范围
.scopes("all");
}
/**
* 认证服务器是玩转token的,那么这⾥配置token令牌管理相关(token此时就是⼀个字符
* 串,当下的token需要在服务器端存储,
* 那么存储在哪⾥呢?都是在这⾥配置)
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.tokenStore(tokenStore()) //token保存在哪
.tokenServices(authorizationServerTokenServices()) //token服务的⼀个描述,可以认为是token⽣成细节的描述,⽐如有效时间多少等
.authenticationManager(authenticationManager) // 指定认证管理器
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 该⽅法⽤于创建tokenStore对象(令牌存储对象)
* token以什么形式存储
*/
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
/**
* 该⽅法⽤户获取⼀个token服务对象(该对象描述了token有效期等信息)
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使⽤默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
// 设置令牌有效时间(⼀般设置为2个⼩时)
defaultTokenServices.setAccessTokenValiditySeconds(20); //access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
WebSecurityConfigurerAdapter
- 认证服务器安全配置类
/**
* 该配置类,主要处理⽤户名和密码的校验等事宜
*/
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
/**
* 注册⼀个认证管理器对象到容器
* 获取token: http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&
* username=admin&password=123456&client_id=client_edu
* endpoint: /oauth/token
* 获取token携带的参数
* client_id:客户端id
* client_secret:客户单密码
* grant_type:指定使⽤哪种颁发类型, password
* username:⽤户名
* password:密码
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
/**
* 密码编码对象(密码不进⾏加密处理)
*
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
//@Autowired
//private PasswordEncoder passwordEncoder;
/**
* 处理⽤户名和密码验证事宜
* 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());
}
}
测试服务器
http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&username=admin&password=123456&client_id=client-edu
- endpoint: /oauth/token
client_id:客户端id
client_secret:客户单密码
grant_type:指定使⽤哪种颁发类型, password
username:⽤户名
password:密码
验证token,因为只有20秒有效期,过期了请请求上面的再次获取
http://localhost:9999/oauth/check_token?token=574fc8af-0714-4274-b499-1d5efbd06dae
刷新
http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client-edu&client_secret=abcxyz&refresh_token=66ec33a7-4fd8-4147-a8e9-c1609186bb80
资源服务器(希望访问被认证的微服务) Resource Server配置
- pom文件同上引入oath
<!--导入spring cloud oauth2依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
<!--引入security对oauth2的支持-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
- application.yml添加
oauth2:
server:
check-token-url: http://localhost:9999/oauth/check_token
- 配置类 继承这个类ResourceServerConfigurerAdapter
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
自定义配置类 ResourceServerConfiger
// 开启资源服务器功能
@EnableResourceServer
开启web访问安全
@EnableWebSecurity
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
/**
* 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验等事宜
*
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
//当前服务资源id
resources.resourceId("autodeliver");
//定义token服务对象(token校验就应该靠token服务对象)
RemoteTokenServices tokenServices = new RemoteTokenServices();
//检验证端点
tokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
//密码, client_id
tokenServices.setClientId("client-edu");
tokenServices.setClientSecret("abcxyz");
resources.tokenServices(tokenServices);
}
/**
* 场景:⼀个服务中可能有很多资源(API接⼝)
* 某⼀些API接⼝,需要先认证,才能访问
* 某⼀些API接⼝,压根就不需要认证,本来就是对外开放的接⼝
* 我们就需要对不同特点的接⼝区分对待(在当前configure⽅法中完成),设置
* 是否需要经过认
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
// 设置session的创建策略(根据需要创建即可)
http.sessionManagement()
//按需要创建
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
//autodeliver为前缀的请求需要认证
.antMatchers("/autodeliver/**").authenticated()
// demo为前缀/的请求需要认证
.antMatchers("/demo/**").authenticated()
//其它的不需要
.anyRequest().permitAll();
}
}
测试
@RequestMapping("/autodeliver")
@RestController
public class AutodeliverController {
@Autowired
private ResumeFeignClient resumeFeignClient;
@GetMapping("/feign/{userId}")
public Integer findfeign(@PathVariable Long userId) {
String requestUrl = "http://edu-service-resume/resume/openstate/"+userId;
System.out.println("获取服务实现 拼接的url : "+requestUrl);
Integer forObject = resumeFeignClient.findDefaultResumeState(userId);
return forObject;
}
}
@RequestMapping("/demo")
@RestController
public class DemoController {
@GetMapping("/test")
public String test() {
return "test";
}
}
@RestController
@RequestMapping("/other")
public class OtherController {
@Autowired
private ResumeFeignClient resumeFeignClient;
@GetMapping("/{userId}")
public Integer demofeign(@PathVariable Long userId) {
String requestUrl = "http://edu-service-resume/resume/openstate/"+userId;
System.out.println("获取服务实现 拼接的url : "+requestUrl);
Integer forObject = resumeFeignClient.findDefaultResumeState(userId);
return forObject;
}
}
带上access_toekn
JWT改造统⼀认证授权中⼼的令牌存储机制
当资源服务和授权服务不在⼀起时资源服务使⽤RemoteTokenServices 远
程请求授权 服务验证token,如果访问量较⼤将会影响系统的性能。
解决上边问题: 令牌采⽤JWT格式即可解决上边的问题,⽤户认证通过会得到⼀个JWT令牌, JWT令牌中已经包括了⽤户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法⾃⾏完成令牌校验,⽆需每次都请求认证 服务完成授权
什么是JWT?
JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519),它定义了⼀种简介的、⾃包含的协议
格式,⽤于 在通信双⽅传递json对象,传递的信息经过数字签名可以被验证和信任。 JWT可以使⽤
HMAC算法或使⽤RSA的公 钥/私钥对来签名,防⽌被篡改
JWT令牌结构
JWT令牌由三部分组成,每部分中间使⽤点(.)分隔,⽐如: xxxxx.yyyyy.zzzzz
- Header
头部包括令牌的类型(即JWT)及使⽤的哈希算法(如HMAC SHA256或RSA),例如
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使⽤Base64Url编码,得到⼀个字符串就是JWT令牌的第⼀部分
- Payload
第⼆部分是负载,内容也是⼀个json对象,它是存放有效信息的地⽅,它可以存放jwt提供的现成
字段,⽐ 如: iss(签发者) ,exp(过期时间戳) , sub(⾯向的⽤户)等,也可⾃定义字段。 此部
分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第⼆部分负载使⽤Base64Url
编码,得到⼀个字符串就是JWT令牌的第⼆部分。 ⼀个例⼦
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- Signature
第三部分是签名,此部分⽤于防⽌jwt内容被篡改。 这个部分使⽤base64url将前两部分进⾏编
码,编码后使⽤点(.)连接组成字符串,最后使⽤header中声明 签名算法进⾏签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
base64UrlEncode(header): jwt令牌的第⼀部分。
base64UrlEncode(payload): jwt令牌的第⼆部分。
secret:签名所使⽤的密钥。
改oauth -server
认证服务器端JWT改造(改造主配置类)
package com.liu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
//启动认证服务
@EnableAuthorizationServer
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
* 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配
* 置
* private String tokenKeyAccess = "denyAll()";
*
* private String checkTokenAccess = "denyAll()";
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
//允许客户端表单提交
security.allowFormAuthenticationForClients()
// 开启端⼝/oauth/token_key的访问权限(允许)
.tokenKeyAccess("permitAll()")
开启端⼝/oauth/check_token的访问权限(允许)
.checkTokenAccess("permitAll()");
}
/**
* 客户端详情配置,
* ⽐如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() // 客户端信息存储在什么地⽅,可以在内存中,可以在数据库⾥
.withClient("client-edu") //添加⼀个client配置,指定其client_id
.secret("abcxyz") // 指定客户端的密码/安全码
// 指定客户端所能访问资源 id清单,此处的资源id是需要在具体的资源服务器上也配置⼀样
.resourceIds("autodeliver")
.authorizedGrantTypes("password", "refresh_token") // 认证类型/令牌颁发模式,可以配置多个在这⾥,但是不⼀定都⽤,具体使哪种⽅式颁发token,需要客户端调⽤的时候传递参数指定
//客户端权限范围
.scopes("all");
}
/**
* 认证服务器是玩转token的,那么这⾥配置token令牌管理相关(token此时就是⼀个字符
* 串,当下的token需要在服务器端存储,
* 那么存储在哪⾥呢?都是在这⾥配置)
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.tokenStore(tokenStore()) //token保存在哪
.tokenServices(authorizationServerTokenServices()) //token服务的⼀个描述,可以认为是token⽣成细节的描述,⽐如有效时间多少等
.authenticationManager(authenticationManager) // 指定认证管理器
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 该⽅法⽤于创建tokenStore对象(令牌存储对象)
* token以什么形式存储
*/
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
/**
* 使用jwtTokenStore
* @return
*/
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
private String signKey = "edu123"; // jwt签名密钥
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// // 签名密钥
jwtAccessTokenConverter.setSigningKey(signKey);
//验证时使用的密码 与签名时一样
jwtAccessTokenConverter.setVerifier(new MacSigner(signKey));
return jwtAccessTokenConverter;
}
/**
* 该⽅法⽤户获取⼀个token服务对象(该对象描述了token有效期等信息)
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使⽤默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
//增加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
// 设置令牌有效时间(⼀般设置为2个⼩时)
defaultTokenServices.setAccessTokenValiditySeconds(20); //access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
- 测试
获取token
http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&username=admin&password=123456&client_id=client-edu
- 验证
http://localhost:9999/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwiZXhwIjoxNTk4NjA0MjAyLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6ImE4ZGViM2I1LTExYTYtNDc4Ny04NTkyLTYzNzY5ODdjZjdjNiIsImNsaWVudF9pZCI6ImNsaWVudC1lZHUiLCJzY29wZSI6WyJhbGwiXX0.SMPdI5VF4jCrwyKLFNMZeEtkfUztFi7OJE2hrX2d7CU
- 刷新
http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client-edu&client_secret=abcxyz&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiYThkZWIzYjUtMTFhNi00Nzg3LTg1OTItNjM3Njk4N2NmN2M2IiwiZXhwIjoxNTk4ODYzMzgyLCJqdGkiOiJlNzM2NjQ3My1kMWYzLTQ4ZTEtOWYxNi04MDgxNjQzN2ZmZDIiLCJjbGllbnRfaWQiOiJjbGllbnQtZWR1In0.N672NJkdfXvTkfo4mRRIVB6UsGeKAkwEk6BtSflBoqM
客户端资源服务改造
package com.liu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
// 开启资源服务器功能
@EnableResourceServer
开启web访问安全
@EnableWebSecurity
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
/**
* 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验等事宜
*
* @param resources
* @throws Exception
*/
// @Override
// public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// // super.configure(resources);
// //当前服务资源id
// resources.resourceId("autodeliver");
// //定义token服务对象(token校验就应该靠token服务对象)
//
// RemoteTokenServices tokenServices = new RemoteTokenServices();
// //检验证端点
// tokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
// //密码, client_id
// tokenServices.setClientId("client-edu");
// tokenServices.setClientSecret("abcxyz");
//
//
// resources.tokenServices(tokenServices);
// }
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// super.configure(resources);
//当前服务资源id
resources.resourceId("autodeliver");
//定义token服务对象(token校验就应该靠token服务对象)
//无状态
resources.tokenStore(tokenStore()).stateless(true);
}
/**
* 使用jwtTokenStore
* @return
*/
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
private String signKey = "edu123"; // jwt签名密钥
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// // 签名密钥
jwtAccessTokenConverter.setSigningKey(signKey);
//验证时使用的密码 与签名时一样
jwtAccessTokenConverter.setVerifier(new MacSigner(signKey));
return jwtAccessTokenConverter;
}
/**
* 场景:⼀个服务中可能有很多资源(API接⼝)
* 某⼀些API接⼝,需要先认证,才能访问
* 某⼀些API接⼝,压根就不需要认证,本来就是对外开放的接⼝
* 我们就需要对不同特点的接⼝区分对待(在当前configure⽅法中完成),设置
* 是否需要经过认
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
// super.configure(http);
// 设置session的创建策略(根据需要创建即可)
http.sessionManagement()
//按需要创建
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
//autodeliver为前缀的请求需要认证
.antMatchers("/autodeliver/**").authenticated()
// demo为前缀/的请求需要认证
.antMatchers("/demo/**").authenticated()
//其它的不需要
.anyRequest().permitAll();
}
}
将信息保存数据
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `edu`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('client_edu_123', 'autodeliver,resume', 'abcxyz', 'all', 'password,refresh_token', NULL, NULL, '7200', '259200', NULL, NULL);
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` char(10) DEFAULT NULL,
`password` char(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (4, 'edu-user', 'iuxyzds');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1
jpa -dao
@Data
@Entity
@Table(name = "users")
public class Users {
@Id
private Long id;
private String username;
private String password;
}
public interface UsersRepository extends JpaRepository<Users,Long> {
Users findByUsername(String username);
}
Service
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UsersRepository usersRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = usersRepository.findByUsername(username);
return new User(users.getUsername(),users.getPassword(),new ArrayList<>());
}
}
配置类
package com.liu.config;
import com.liu.serivice.JdbcUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
/**
* 该配置类,主要处理⽤户名和密码的校验等事宜
*/
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
/**
* 注册⼀个认证管理器对象到容器
* 获取token: http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&
* username=admin&password=123456&client_id=client_edu
* endpoint: /oauth/token
* 获取token携带的参数
* client_id:客户端id
* client_secret:客户单密码
* grant_type:指定使⽤哪种颁发类型, password
* username:⽤户名
* password:密码
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
/**
* 密码编码对象(密码不进⾏加密处理)
*
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
//@Autowired
//private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
/**
* 处理⽤户名和密码验证事宜
* 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(userDetailsService);
}
}
- 启动类
@EnableDiscoveryClient
@SpringBootApplication
@EntityScan("com.liu.pojo")
public class OauthServerApplication9999 {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication9999.class,args);
}
}
测试-登录获取accesstoken
http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&username=edu-user&password=iuxyzds&client_id=client_edu_123
使用数据库中的用户名与密码
接入网关
cloud:
gateway:
routes: # 路由可以有多个
- id: service-oauth-router
uri: lb://edu-cloud-oauth-server
predicates:
- Path=/oauth/**
http://localhost:9003/oauth/token?client_secret=abcxyz&grant_type=password&username=edu-user&password=iuxyzds&client_id=client_edu_123
向jwt中放入自定义的信息
认证服务中
认证服务器⽣成JWT令牌时存⼊扩展信息(⽐如clientIp)
继承DefaultAccessTokenConverter类,重写convertAccessToken⽅法存⼊扩展信息
package com.liu.convertor;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Component
public class EduAccessTokenConvertor extends DefaultAccessTokenConverter {
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map<String, String> stringMap = (Map<String, String>)super.convertAccessToken(token, authentication);
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
// 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种方式获取的并不是真实的浏览器客户端ip)
String remoteAddr = request.getRemoteAddr();
stringMap.put("clientIp",remoteAddr);
return stringMap;
}
}
在配置类中 -JwtAccessTokenConverter 加入
package com.liu.config;
import com.liu.convertor.EduAccessTokenConvertor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.sql.DataSource;
@Configuration
//启动认证服务
@EnableAuthorizationServer
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private EduAccessTokenConvertor eduAccessTokenConvertor;
/**
* 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
* 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配
* 置
* private String tokenKeyAccess = "denyAll()";
*
* private String checkTokenAccess = "denyAll()";
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
//允许客户端表单提交
security.allowFormAuthenticationForClients()
// 开启端⼝/oauth/token_key的访问权限(允许)
.tokenKeyAccess("permitAll()")
开启端⼝/oauth/check_token的访问权限(允许)
.checkTokenAccess("permitAll()");
}
/**
* 客户端详情配置,
* ⽐如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() // 客户端信息存储在什么地⽅,可以在内存中,可以在数据库⾥
// .withClient("client-edu") //添加⼀个client配置,指定其client_id
// .secret("abcxyz") // 指定客户端的密码/安全码
// // 指定客户端所能访问资源 id清单,此处的资源id是需要在具体的资源服务器上也配置⼀样
// .resourceIds("autodeliver")
// .authorizedGrantTypes("password", "refresh_token") // 认证类型/令牌颁发模式,可以配置多个在这⾥,但是不⼀定都⽤,具体使哪种⽅式颁发token,需要客户端调⽤的时候传递参数指定
// //客户端权限范围
// .scopes("all");
//改造为从数据库
clients.withClientDetails(createJdbcClientDetailsService());
}
/**
* 注入数据
*
*/
@Autowired
private DataSource dataSource;
@Bean
public JdbcClientDetailsService createJdbcClientDetailsService() {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
return jdbcClientDetailsService;
}
/**
* 认证服务器是玩转token的,那么这⾥配置token令牌管理相关(token此时就是⼀个字符
* 串,当下的token需要在服务器端存储,
* 那么存储在哪⾥呢?都是在这⾥配置)
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.tokenStore(tokenStore()) //token保存在哪
.tokenServices(authorizationServerTokenServices()) //token服务的⼀个描述,可以认为是token⽣成细节的描述,⽐如有效时间多少等
.authenticationManager(authenticationManager) // 指定认证管理器
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 该⽅法⽤于创建tokenStore对象(令牌存储对象)
* token以什么形式存储
*/
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
/**
* 使用jwtTokenStore
* @return
*/
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
private String signKey = "edu123"; // jwt签名密钥
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// // 签名密钥
jwtAccessTokenConverter.setSigningKey(signKey);
//验证时使用的密码 与签名时一样
jwtAccessTokenConverter.setVerifier(new MacSigner(signKey));
//注入jwtConverter
jwtAccessTokenConverter.setAccessTokenConverter(eduAccessTokenConvertor);
return jwtAccessTokenConverter;
}
/**
* 该⽅法⽤户获取⼀个token服务对象(该对象描述了token有效期等信息)
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使⽤默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
defaultTokenServices.setTokenStore(tokenStore());
//增加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
// 设置令牌有效时间(⼀般设置为2个⼩时)
defaultTokenServices.setAccessTokenValiditySeconds(20); //access_token就是我们请求资源需要携带的令牌
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
在资源服务中获取自定义的信息
package com.liu.converter;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Component
public class EduAccessTokenConvertor extends DefaultAccessTokenConverter {
// @Override
// public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
// Map<String, String> stringMap = (Map<String, String>)super.convertAccessToken(token, authentication);
//
// HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
//
//
// // 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种方式获取的并不是真实的浏览器客户端ip)
// String remoteAddr = request.getRemoteAddr();
// stringMap.put("clientIp",remoteAddr);
//
// return stringMap;
// }
/**
* 可以在Controller中获取到
* @param map
* @return
*/
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
oAuth2Authentication.setDetails(map);
return oAuth2Authentication;
}
}
package com.liu.config;
import com.liu.converter.EduAccessTokenConvertor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
// 开启资源服务器功能
@EnableResourceServer
开启web访问安全
@EnableWebSecurity
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
@Autowired
private EduAccessTokenConvertor eduAccessTokenConvertor;
/**
* 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验等事宜
*
* @param resources
* @throws Exception
*/
// @Override
// public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// // super.configure(resources);
// //当前服务资源id
// resources.resourceId("autodeliver");
// //定义token服务对象(token校验就应该靠token服务对象)
//
// RemoteTokenServices tokenServices = new RemoteTokenServices();
// //检验证端点
// tokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
// //密码, client_id
// tokenServices.setClientId("client-edu");
// tokenServices.setClientSecret("abcxyz");
//
//
// resources.tokenServices(tokenServices);
// }
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// super.configure(resources);
//当前服务资源id
resources.resourceId("autodeliver");
//定义token服务对象(token校验就应该靠token服务对象)
//无状态
resources.tokenStore(tokenStore()).stateless(true);
}
/**
* 使用jwtTokenStore
* @return
*/
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
private String signKey = "edu123"; // jwt签名密钥
/**
* 返回jwt令牌转换器(帮助我们生成jwt令牌的)
* 在这里,我们可以把签名密钥传递进去给转换器对象
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// // 签名密钥
jwtAccessTokenConverter.setSigningKey(signKey);
//验证时使用的密码 与签名时一样
jwtAccessTokenConverter.setVerifier(new MacSigner(signKey));
jwtAccessTokenConverter.setAccessTokenConverter(eduAccessTokenConvertor);
return jwtAccessTokenConverter;
}
/**
* 场景:⼀个服务中可能有很多资源(API接⼝)
* 某⼀些API接⼝,需要先认证,才能访问
* 某⼀些API接⼝,压根就不需要认证,本来就是对外开放的接⼝
* 我们就需要对不同特点的接⼝区分对待(在当前configure⽅法中完成),设置
* 是否需要经过认
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
// super.configure(http);
// 设置session的创建策略(根据需要创建即可)
http.sessionManagement()
//按需要创建
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
//autodeliver为前缀的请求需要认证
.antMatchers("/autodeliver/**").authenticated()
// demo为前缀/的请求需要认证
.antMatchers("/demo/**").authenticated()
//其它的不需要
.anyRequest().permitAll();
}
}
@Slf4j
@RequestMapping("/demo")
@RestController
public class DemoController {
@GetMapping("/test")
public String test() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
Object details = authentication.getDetails();
log.info("details {} ",details);
return "test";
}
}