基于Oauth2发放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,对Token进行解析就可以获取自己定义的相关信息,例如userId
,而不必根据Token再去查询相关联的数据。
密签:签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。
可扩展:可以自定义相关参数
代码
修改TokenStoreConfig
package 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/网站上进行解析
自定义增强器
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;
}
}
解析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