springboot2.0整合OAuth2并使⽤JWT作为token。
之前实现了 刚好这段时间有空,乘机整合下OAuth2。记录下当中遇到的问题和处理⽅式。
什么是OAuth2?
具体代码实现
POM⽂件
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
授权服务器
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final String CLIENT_ID = "client"; //客户端
private static final String CLIENT_SECRET = "123456"; //secret客户端安全码
private static final String GRANT_TYPE_PASSWORD = "password"; // 密码模式授权模式
private static final String AUTHORIZATION_CODE = "authorization_code"; //授权码模式授权码模式使⽤到了回调地址,是最为复杂的⽅式,通常⽹站中经常出现的微博,qq第三⽅登录,都会采⽤这个形式。
private static final String REFRESH_TOKEN = "refresh_token"; //
private static final String IMPLICIT = "implicit"; //简化授权模式
private static final String GRANT_TYPE = "client_credentials"; //客户端模式
private static final String SCOPE_WEB = "web"; //授权范围 web端
private static final String SCOPE_IOS = "ios"; //授权范围 ios端
private static final String SCOPE_ANDROID = "android";
private static final String SCOPE_BOOT = "boot"; //授权范围项⽬名称
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*24*60*60; //token 有效时间⼀个⽉
private static final int REFRESH_TOKEN_VALIDITY_SECONDS = 30*24*60*60; //刷新token有效时间⼀个⽉
/**
* 描述:注⼊密码加密编码器进⾏密码加密
*/
@Autowired
BCryptPasswordEncoder passwordEncoder;
/**
* 描述:注⼊⽤户信息处理类处理⽤户账号信息
*/
@Autowired
UserDetailsServiceImpl userDetailService;
/**
* 描述:注⼊token⽣成器处理token的⽣成⽅式
*/
@Autowired
TokenStore tokenStore;
/
**
* 描述: 注⼊AuthenticationManager管理器
*/
@Autowired
AuthenticationManager authenticationManager;
/**
* 描述: 注⼊jwtAccessTokenConverter 增强token
*/
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
String secret = new BCryptPasswordEncoder().encode(CLIENT_SECRET); // ⽤ BCrypt 对密码编码
//配置客户端信息
clients.inMemory() // 使⽤in-memory存储
.withClient(CLIENT_ID) //client_id⽤来标识客户的Id
.authorizedGrantTypes(AUTHORIZATION_CODE,GRANT_TYPE, REFRESH_TOKEN,GRANT_TYPE_PASSWORD,IMPLICIT) //允许授权类型 .scopes(SCOPE_WEB,SCOPE_IOS,SCOPE_ANDROID,SCOPE_BOOT) //允许授权范围
.authorities("ROLE_CLIENT") //客户端可以使⽤的权限
.secret(secret) //secret客户端安全码
.autoApprove(true) // 为true 则不会被重定向到授权的页⾯,也不需要⼿动给请求授权,直接⾃动授权成功返回code
.
accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS) //token 时间秒
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);//刷新token 时间秒
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 允许表单登录
.allowFormAuthenticationForClients()
// 密码加密编码器
.passwordEncoder(passwordEncoder)
// 允许所有的checkToken请求
.
checkTokenAccess("permitAll()");
}
/**
* 配置令牌
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 认证管理器 - 在密码模式必须配置
.
authenticationManager(authenticationManager)
// ⾃定义校验⽤户service
.userDetailsService(userDetailService)
// 是否能重复使⽤ refresh_token
.reuseRefreshTokens(false);
// 设置令牌增强 JWT 转换
TokenEnhancerChain enhancer = new TokenEnhancerChain();
enhancer.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
}
}
资源服务器
@Configuration
@EnableResourceServer
public class OAuth2ResourceConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().csrf().disable();
// 配置不登录可以访问 - 放⾏路径配置
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.
authorizeRequests();
registry.antMatchers("/login","/oauth/**").permitAll();
registry.anyRequest().authenticated();
}
}
token处理
@Configuration
public class TokenConfig {
/** JWT密钥 */
private String signingKey = "fastboot";
/**
* JWT 令牌转换器
* @return
*/
@Bean("jwtAccessTokenConverter")
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter(){
/**
* ⽤户信息JWT加密
*/
@Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
UserInfo user = (UserInfo) UserAuthentication().getPrincipal();
Set<String> tokenScope = Scope();
String scopeTemp = " ";
if(tokenScope!=null&&tokenScope.size()>0){
scopeTemp=tokenScope.iterator().next();
}
String scope =scopeTemp;
//将额外的参数信息存⼊,⽤于⽣成token
Map<String, Object> data = new HashMap<String, Object>(4){{
put("userId", UserId());
put("username", Username());
put("email", Email());
put("roleDtos",RoleDtos());
put("nickName", NickName());
put("authorities", Authorities());
put("scope",scope);
}};
//⾃定义TOKEN包含的信息
token.setAdditionalInformation(data);
de(accessToken, authentication);
}
/
**
* ⽤户信息JWT
*/
@Override
protected Map<String, Object> decode(String token) {
//解析请求当中的token 可以在解析后的map当中获取到上⾯加密的数据信息
Map<String, Object> decode = super.decode(token);
Long userId = (("userId");
String username = (("username");
String email = (("email");
String nickName = (("nickName");
String scope = (("scope");
List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();
//注意这⾥获取到的权限虽然数据库存的权限是 "sys:menu:add" 但是这⾥就变成了"{authority=sys:menu:add}" 所以使⽤@PreAuthorize("hasAutho rity('{authority=sys:menu:add}')")
List<LinkedHashMap<String,String>> authorities =(List<LinkedHashMap<String,String>>) ("authorities");
for (LinkedHashMap<String, String> authority : authorities) {
SimpleGrantedAuthority grantedAuthority = new OrDefault("authority", "N/A"));
grantedAuthorityList.add(grantedAuthority);
}
UserInfo userInfo =new UserInfo(username,"N/A",userId, grantedAuthorityList);
userInfo.setNickName(nickName);
userInfo.setEmail(email);
//需要将解析出来的⽤户存⼊全局当中,不然⽆法转换成⾃定义的user类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userInfo,null, grantedAuthorityList );
decode.put("user_name",userInfo);
return decode;
}
};
jwt.setSigningKey(signingKey);
return jwt;
}
/**
* 配置 token 如何⽣成
* 1. InMemoryTokenStore 基于内存存储
* 2. JdbcTokenStore 基于数据库存储
* 3. JwtTokenStore 使⽤ JWT 存储该⽅式可以让资源服务器⾃⼰校验令牌的有效性⽽不必远程连接认证服务器再进⾏认证
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
public String getSigningKey() {
return signingKey;
}
public void setSigningKey(String signingKey) {
this.signingKey = signingKey;
}
}
controller接⼝
@RestController
@RequestMapping("/oauth")
public class OauthController {
@Autowired
TokenEndpoint tokenEndpoint;
@PostMapping(value = "/token")
public ResultInfo<OAuth2AccessToken> token(Principal principal, @RequestParam Map<String, String> parameters) throws Exception {
ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
OAuth2AccessToken token = Body();
web端登录// TODO 可以考虑将返回的TOKEN信息存⼊redis或者数据库
return ResultInfo.success(token);
}
@PostMapping("/t1")
@PreAuthorize("hasAuthority('{authority=sys:menu:add}')")
public String getDemo(String name){
Context() == null) {
return null;
}
Authentication authentication = Context().getAuthentication();
UserInfo userInfo = (UserInfo) Principal();
// return ResultInfo.success(userInfo);
String();
}
}
上⾯基本上就是完整的代码了。其他的类如:UserDetailsServiceImpl,UserInfo 略
遇到的问题
1 token过期时间设置 可以在OAuth2ServerConfiguration 当中设置accessTokenValiditySeconds(秒) 也可以在TokenConfig ⾥⾯进⾏jwt加密的时候进⾏设置,token.setExpiration(); 设置后会覆盖OAuth2ServerConfiguration 当中的。
2 权限不匹配问题 虽然数据库存的权限是 “sys:menu:add” 但是oauth2取的时候变成了"{authority=sys:menu:add}" 所以使⽤接⼝上使⽤@PreAuthorize(“hasAuthority(’{authority=sys:menu:add}’)”)进⾏权限匹配。
3 serurity的User类⽆法转换为⾃定义的user⼦类的问题,需要在 JWT解密的时候,重新构建然后存⼊全局当中。(PS:⽆法在获取token当中获取到⾃定义的user⼦类)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论