spring security oauth2 + jwt认证实现,认证通过返回access_token,用户相关信息可以通过access_token的解析获取。但是SecurityContextHolder.getContext().getAuthentication().getName()获取到的username是client_id。
在不使用jwt的情况下,正常登录认证后,通过SecurityContextHolder.getContext().getAuthentication().getName()获取到的username就是登录用户名。
一般情况下,不处理此种情况对于用户资源访问没有任何影响。但是因为在一个项目中使用activiti7的工作流,工作流执行是因为权限问题,导致工作流执行异常,最终debug发现,因为SecurityContextHolder.getContext().getAuthentication().getName()是client_id,不是真正登录后的用户名导致。
解决方案:
修改认证,及继承了AuthorizationServerConfigurerAdapter类的一个自定义类,比如AuthorizationServerConfig类,源码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Resource(name = "keyProp")
private KeyProperties keyProperties;
//重点代码,实现自定义的凭证转化
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
/** 客户端配置 */
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).clients(clientDetails());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.accessTokenConverter(jwtAccessTokenConverter()) //重点代码
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
;
}
/****
* JWT令牌转换器
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
/**
* getLocation 证书路径 microservice.jks
* getSecret 证书秘钥 microservice
* getAlias 证书别名 microservice
* getPassword 证书密码 microservice
*/
KeyPair keyPair = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(),
keyProperties.getKeyStore().getSecret().toCharArray())
.getKeyPair(
keyProperties.getKeyStore().getAlias(),
keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
/** 配置自定义的 CustomUserAuthenticationConverter */
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
//允许表单认证
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
对于不需要使用工作流服务,资源服务配置可使用以下源码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
* 描述
*
* @author carter
* @version 1.0
* @package *
* @since 1.0
*/
@Configuration
// 开启 资源服务器(标识他是一个oauth2中的资源服务器)
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/***
* 定义JJwtAccessTokenConverter 用来校验令牌
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
*
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//放行 用户注册的请求
//其他的请求 必须有登录之后才能访问 (校验token合法才可以访问)
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/user/add", "/user/login", "/**"). //配置地址放行
permitAll()
.anyRequest()
.authenticated(); //其他地址需要认证授权
}
}
针对以activiti7为工作流服务,需要做相应的变更,并且需要自定义一个类继承JwtAccessTokenConverter,资源服务配置使用以下源码:
import com.common.jwt.CustomAccessTokenConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
* 描述
*
* @author carter
* @version 1.0
* @package *
* @since 1.0
*/
@Configuration
// 开启 资源服务器(标识他是一个oauth2中的资源服务器)
//@EnableResourceServer
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/***
* 定义JJwtAccessTokenConverter 用来校验令牌
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//重点代码,主要是为了解决jwt认证获取到登录用户名是client_id
CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
*
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//放行 用户注册的请求
//其他的请求 必须有登录之后才能访问 (校验token合法才可以访问)
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers("/**"). //配置地址放行
permitAll()
.anyRequest()
.authenticated(); //其他地址需要认证授权
}
}
此处源码主要是为了解决jwt认证获取到登录用户名是client_id,重新封装登录认证信息,最终SecurityContextHolder.getContext().getAuthentication().getName()是。
import cn.hutool.core.collection.CollectionUtil;
import com.common.constants.Constants;
import com.google.common.collect.Lists;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 项目名称:common
* 类 名 称:CustomAccessTokenConverter
* 类 描 述:TODO
* 创建时间:2021/3/21 下午11:42
* 创 建 人:chenyouhong
*/
public class CustomAccessTokenConverter extends JwtAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
List<String> authorities = (List)claims.get("authorities");
List<SimpleGrantedAuthority> simpleGrantedAuthorities = Lists.newArrayList();
if (CollectionUtil.isNotEmpty(authorities)) {
simpleGrantedAuthorities.addAll(authorities.stream().map(e -> new SimpleGrantedAuthority(e)).collect(Collectors.toList()));
}
String password = Constants.BLANK;
if (claims.get("password") != null) {
password = (String)claims.get("password");
}
UserJwt userDetails = new UserJwt((String)claims.get("userCode"), password, simpleGrantedAuthorities);
OAuth2Request request = new OAuth2Request((Map)null, (String)claims.get("client_id"), simpleGrantedAuthorities, true, (Set)null, (Set)null, (String)null, (Set)null, (Map)null);
//将提取的值principal作为构造函数参数
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, "N/A", simpleGrantedAuthorities);
token.setDetails(claims);
return new OAuth2Authentication(request, token);
}
}