客户端模式:
请求一个接口,咱们的后端服务A直接请求验证服务B拿到token,服务A再用token访问资源服务C。
这里用模块化开发,一个资源服务器,一个验证服务器
依赖
Springboot 版本为2.3.3.RELEASE
<!--Security + oauth2 + jwt -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.3</version>
</dependency>
<!--redis + 连接池-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
yml
server:
port: 9910
myoauth2:
clientId: admin1
clientSecret: 123456
tokenEndpoint: http://localhost:9910
1.配置一个验证服务器 9910 端口
/**
* 验证服务器;EnableAuthorizationServer注解表示是个验证服务器
*/
@Configuration
@EnableAuthorizationServer
public class Config_Authorization extends AuthorizationServerConfigurerAdapter {
@Value("${myoauth2.clientId}")
private String clientId;
@Value("${myoauth2.clientSecret}")
private String clientSecret;
private static final String DEMO_RESOURCE_ID = "order";
@Resource
private AuthenticationManager authenticationManager;
@Resource
private BCryptPasswordEncoder passwordEncoder;
//============redis存储token===============
// @Resource
// private RedisConnectionFactory redisConnectionFactory;
//============redis存储token===============
//=============JWT存储token==================
@Resource
private TokenStore tokenStore;
@Resource
private JwtAccessTokenConverter accessTokenConverter;
@Resource
private Jwt_TokenEnhancer jwtTokenEnhancer;
//=============JWT存储token==================
/**
* 访问端点配置
* 配置授权authorization以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置Redis存储token
//endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory));
//配置Jwt存储token + Jwt自定义增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, accessTokenConverter));
endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter).tokenEnhancer(tokenEnhancerChain);
//配置管理器允许GET和POST请求端点oauth/token获取Token
endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 授权端点开放,配置令牌端点(Token Endpoint)的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()") //开启/oauth/token_key验证端口无权限访问
.checkTokenAccess("isAuthenticated()") //开启/oauth/check_token验证端口认证权限访问
.allowFormAuthenticationForClients(); //允许表单认证
}
/**
* 配置客户端详情服务
* 客户端详情信息在这里进行初始化, 通过数据库来存储调取详情信息
* 在验证服务器为客户端client配置resourceIds的目的是:限制某个client可以访问的资源服务。
* 当请求发送到Resource Server的时候会携带access_token,
* Resource Server会根据access_token找到client_id,进而找到该client可以访问的resource_ids。
* 如果resource_ids包含ResourceServer自己设置ResourceID,这关就过去了,就可以继续进行其他的权限验证
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//使用内存模式; 也可以配置客户端存储到数据库DB,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
clients.inMemory()
.withClient(clientId) //client_id
.secret(passwordEncoder.encode(clientSecret)) //client_密码
.resourceIds(DEMO_RESOURCE_ID) //配置资源的id
.authorizedGrantTypes("client_credentials") //授权类型:客户端模式
.scopes("all") //配置申请的权限范围
.authorities("client"); //客户端可以使用的权限
}
}
2. Security配置类
@Configuration
@EnableWebSecurity
public class Config_WebSecurity extends WebSecurityConfigurerAdapter {
/**
* 配置拦截保护的请求径
* permitAll() 表示任意用户可访问
* anyRequest() 表示所有请求
* authenticated() 表示已登录用户才能访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf() //不启用跨站请求伪造
.disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**") //放行这些以"/login/","/oauth/"开头请求
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
/**
* 配置验证管理器
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置密码加密器
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
验证服务器用JWT存储Token,返回JWT格式的Token
/**
* 使用Jwt存储token的配置
*/
@Configuration
public class Jwt_TokenStore {
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
@Bean
public Jwt_TokenEnhancer jwtTokenEnhancer() {
return new Jwt_TokenEnhancer();
}
}
JWT内容自定义
/**
* Jwt内容增强器
*/
public class Jwt_TokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("我的信息", "abcddd");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
再配置一个资源服务器 9911 端口
@Slf4j
@Configuration
@EnableResourceServer
public class Config_Resource extends ResourceServerConfigurerAdapter {
@Value("${uaa.clientId}")
private String clientId;
@Value("${uaa.clientSecret}")
private String clientSecret;
@Value("${uaa.tokenEndpoint}")
private String tokenEndpoint;
/**
* ResourceID资源的标识
*/
private static final String DEMO_RESOURCE_ID = "order";
/**
* 在每个ResourceServer实例上设置resourceId,该resourceId作为该资源的唯一标识
* 验证服务器给Client第三方客户端授权时,可以设置这个Client可以访问哪些Resource-Server资源服务
* 如没有设置就是对所有的Resource-Server都有访问权限
*
* resources.resourceId(DEMO_RESOURCE_ID) //为每个ResourceServer(一个微服务实例)设置一个ResourceID
* resources.stateless(true) //标记以指示在这些资源上仅允许基于令牌的身份验证
* resources.tokenStore(xxx) //token的存储方式
* resources.tokenExtractor(xxx) //token获取方式,默认为BearerTokenExtractor
* resources.authenticationEntryPoint(xxx); //配置自定义的认证异常处理返回类
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
/**
* 用来配置拦截保护的请求
* 这里可以代替Spring Security同名方法配置
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //禁用了csrf功能,关跨域保护
.authorizeRequests() //限定签名成功的请求
.antMatchers("/order/**").authenticated() //必须认证过后才可以访问
.anyRequest().permitAll() //其他没有限定的请求允许随意访问
.and().anonymous(); //对于没有配置权限的其他请求允许匿名访问
}
/**
* RemoteTokenServices是用于向远程认证服务器验证token,同时获取token对应的用户的信息
* 通过RestTemplate调用远程服务,我们在使用这个类时,要设置checkTokenEndpointUrl、clientId、clientSecret等。
* 只需要显示注入RemoteTokenServices remoteTokenServices()的Bean就可以调用授权服务器的/oauth/check_token端点查询token的合法性,之后返回其信息
* 设置客户端配置的值
*/
@Primary
@Bean
public RemoteTokenServices remoteTokenServices() {
final RemoteTokenServices tokenServices = new RemoteTokenServices();
//设置授权服务器check_token(验证token)端点完整地址
tokenServices.setCheckTokenEndpointUrl(tokenEndpoint+"/oauth/check_token");
//设置client_id与secret,注意client_secret值不能使用passwordEncoder加密
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
return tokenServices;
}
}
已经配好了,启动两个服务
验证服务器9910,资源服务器9911
上面的保护资源的是order,要请求资源服务器order接口时需要token验证
这里先访问资源服务器的接口
首先用户要访问order资源,
我们后台服务器A 通过过滤器 先去验证服务器B拿Token
http://localhost:9910/oauth/token?grant_type=client_credentials&scope=select&client_id=admin1&client_secret=123456
这就要求验证服务器B对我们的服务器A很信任
请求Token返回的结果中有access_token
这时服务A再访问资源服务器C,请求头中自动添加这个参数:Authorization 值为access_token 或者 "Bearer "+access_token;就可以访问order了
当然资源服务器C收到了带Token的请求先会去验证服务器B验证一下Token对不对
就是RemoteTokenServices 这个方法,远程调用验证端点check_token了
这种模式不常用,一般用密码模式和授权码模式