基于Oauth2发放token,在请求头中携带token作为用户身份鉴定
Token参数配置
-
定义可发放令牌的应用信息
package com.cong.security.core.properties; import lombok.Data; @Data public class OAuth2ClientProperties { //应用id private String clientId; //密码 private String clientSecret; //token过期时间 private int accessTokenValiditySeconds; //支持类型 private String[] authorizedGrantTypes; //权限类型 private String[] scopes; }
在OAuth2Properties类中添加
private OAuth2ClientProperties[] clients = {};
yml配置文件添加配置:
-
修改
MyAuthorizationServerConfig
配置package com.cong.security.app.authentication; import com.cong.security.core.properties.OAuth2ClientProperties; import com.cong.security.core.properties.SecurityProperties; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder; 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; @Configuration @EnableAuthorizationServer public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired(required = false) private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private SecurityProperties securityProperties; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 可以给哪些应用发放令牌 // 可以存储在内存中,也可以存储在数据库中JdbcClientDetailsServiceBuilder----->clients.jdbc(dataSource) InMemoryClientDetailsServiceBuilder builder = clients.inMemory(); if (ArrayUtils.isNotEmpty(securityProperties.getOAuth2().getClients())) { for (OAuth2ClientProperties config : securityProperties.getOAuth2().getClients()) { builder.withClient(config.getClientId()).secret(config.getClientSecret())// 设置client-id以及client-secret .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds())// 设置token过期时间,单位S .refreshTokenValiditySeconds(config.getAccessTokenValiditySeconds() * 3) // 刷新token过期时间是token自身的三倍 .authorizedGrantTypes(config.getAuthorizedGrantTypes())// 支持的授权模式 .scopes(config.getScopes());// 可以发出去的权限 } } } }
-
控制令牌存储
APP模块下不存在Session,用户一次登录永久有效,而服务端需要经常性的进行版本迭代,服务中断,让用户重新登陆明显不合适,方法是将令牌存储到持久化存储里面(数据库、Redis),后面会使用无状态Token,即服务端不对Token进行存储。
使用Redis进行存储:
进行如上配置之后执行登录操作,在对应的Redis数据库中即可查看相应的数据:
JWT替换默认Token
-
JWT
自包含:可以包含相关的用户信息,最基本的数据关联,用户执行相关操作时需要有对应的权限,此时请求头中会携带Token,对Token进行解析就可以获取自己定义的相关信息,例如userId
,而不必根据Token再去查询相关联的数据。
密签:签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。
可扩展:可以自定义相关参数 -
代码
修改TokenStoreConfigpackage com.cong.security.app.social; import javax.sql.DataSource; import com.cong.security.core.properties.SecurityProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; @Configuration public class TokenStoreConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Autowired private DataSource dataSource; //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为jdbc时当前配置生效 @Bean @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "jdbc") public TokenStore jdbcTokenStore() { return new JdbcTokenStore(dataSource); } //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为redis时当前配置生效 @Bean @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "redis") public TokenStore redisTokenStore() { return new RedisTokenStore(redisConnectionFactory); } //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为jwt时当前配置生效 @Configuration @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "jwt", matchIfMissing = true) public static class JwtTokenConfig { @Autowired private SecurityProperties securityProperties; //处理token存储 @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } //处理token生成逻辑 @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); // 设置签名密钥 accessTokenConverter.setSigningKey(securityProperties.getOAuth2().getJwtSigningKey()); return accessTokenConverter; } } }
修改MyAuthorizationServerConfig
此时执行登录操作返回的Token为JWT生成的Token:
在https://www.jsonwebtoken.io/网站上进行解析
扩展及解析Token
-
自定义增强器
package com.cong.security.app.social; import java.util.HashMap; import java.util.Map; import com.cong.security.core.social.MySocialUser; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenEnhancer; public class MyJwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> map = new HashMap<>(); MySocialUser mySocialUser = (MySocialUser) authentication.getPrincipal(); // 从数据库中根据手机号之类信息查询出 map.put("userId", mySocialUser.getId()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map); return accessToken; } }
-
配置到JwtTokenConfig中
-
修改MyAuthorizationServerConfig配置类
此时登录接口返回token参数中包含自定义参数
-
解析Token
添加依赖(在login模块):<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency>
编写token解析类:
package com.cong.security.config; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import lombok.extern.slf4j.Slf4j; @Slf4j public class LoginUser { /** * 从token中解析出当前账号标识 */ public static String getUserId() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String header = request.getHeader("Authorization"); String token = StringUtils.substringAfter(header, "bearer "); Claims claims = null; // 用户在系统内标识 String userId = null; try { claims = Jwts.parser().setSigningKey("mydev".getBytes("UTF-8")).parseClaimsJws(token).getBody(); userId = (String) claims.get("userId"); } catch (Exception e) { userId = null; } return userId; } }
随便提供一个接口:
使用登录接口返回的token作为请求头请求上面的接口即可返回当前用户对应的用户标识,解析Token成功。 -
Token刷新
当用户Token失效之后无需用户重新登录,可以使用认证时返回的refresh_token获取新的有效Token,新的Token中用户身份有效期都是全新的,可以使用在用户角色变更或者用户账号锁死等情况中。
如果出现token刷新报错401,查看文章:SpringSecurity升级之后token刷新接口401