认证服务器
1.实现认证服务器
![](https://img.haomeiwen.com/i11176703/3a6dd92ed67be8a0.png)
![](https://img.haomeiwen.com/i11176703/319371bd572ef9ec.png)
OAuth核心
![](https://img.haomeiwen.com/i11176703/860575121118a91d.png)
![](https://img.haomeiwen.com/i11176703/44a4017a04d4bf36.png)
/oauth/authorize:验证接口, AuthorizationEndpoint
/oauth/token:获取token
/oauth/confirm_access:用户授权
/oauth/error:认证失败
/oauth/check_token:资源服务器用来校验token
/oauth/token_key:jwt模式下获取公钥;位于:TokenKeyEndpoint ,通过 JwtAccessTokenConverter 访问key
- ClientDetailService:读取Client信息;
-
tokenRequst:封装了其他的参数,比如如果是密码模式的话,其他信息比如用户名、密码都封装在这里。同时会把ClientDetails也封装在TokenReuquest里。
image.png
- TokenRequest会调用TokenGranter,这个接口里封装了四种授权模式不同的实现,会根据grantType挑一个具体的实现来进行令牌生成的逻辑;
- 不管是哪个生成逻辑,都会生成OAuth2Request(是ClientDetails和TokenRequest的整合)和Authentication(通过UserDetailsService读取出来的用户信息)
-OAuth2Request和Authentication最终会生成OAuth2Authentication对象,包含了是哪个第三方应用 ,授权模式,授权用户的信息,授权的参数等都会封装在这个对象里; - OAuth2Authentication传递给AuthenticationServerTokenServices最终生成OAuth2AccessToken;
-
调用
认证会调用UserDetailService
-
DefaultTOkenServices生成token的逻辑
同一个用户,假如再次获取token,其token没过期,会返回之前的token
重构用户名密码登录
![](https://img.haomeiwen.com/i11176703/2b749934e7e7e205.png)
-
不管登录请求是什么、只要登录成功了,最终都会到一个AuthenticationSuccessHandler
可以用到的逻辑
在这个handler里之前返回的是用户信息,现在要返回TokenServices生成的token信息
// 在这个handler里要生成token返回用户,需要构建OAuth2Request,Authentication对象已经存在这个方法里
/**
*
*/
package com.imooc.security.browser.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginResponseType;
import com.imooc.security.core.properties.SecurityProperties;
/**
* @author zhailiang
*
*/
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.authentication.
* AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
* HttpServletRequest, javax.servlet.http.HttpServletResponse,
* org.springframework.security.core.Authentication)
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
if (LoginResponseType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
![](https://img.haomeiwen.com/i11176703/1906972c2ad88fa6.png)
令牌配置
/**
* 认证服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class ImoocAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private SecurityProperties securityProperties;
@Autowired
private TokenStore tokenStore;
@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired(required = false)
private TokenEnhancer jwtTokenEnhancer;
//端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
// 如果jwt转换器和jwt增强器都存在
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
// 将其加入到增强链上
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
/**
* 客户端配置,认证服务器会给哪些第三方应用颁发token
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// // 存储在内存中,也可以设置在数据库中
// clients.inMemory()
// // 设置clientid
// .withClient("earthchen")
// // 设置clientsecret
// .secret("earthchensecret")
// // 设置令牌过期时间
// .accessTokenValiditySeconds(7200)
// // 允许的授权模式
// .authorizedGrantTypes("refresh_token", "authorization_code", "password")
// // 配置oauth能获取的权限
// .scopes("all")
// .and()
// // 第二个app
// .withClient("earthchen_web");
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
builder.withClient(config.getClientId())
.secret(config.getClientSecret())
.accessTokenValiditySeconds(config.getAccessTokenValidateSeconds())
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
// 设置刷新令牌的过期时间
.refreshTokenValiditySeconds(2592000)
.scopes("all", "write", "read");
}
}
}
}
- 设置令牌的存储,负责令牌的存取。将其放在redis里。配置完就在上面的端点配置里配置tokenStore
@Configuration
public class TokenStoreConfig {
/**
* redis连接工厂
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 使用redis存储token的配置,只有在imooc.security.oauth2.tokenStore配置为redis时生效
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "earthchen.security.oauth2",
name = "storeType",
havingValue = "redis")
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
/**
* jwt的配置
*
* 使用jwt时的配置,默认生效
*/
@Configuration
@ConditionalOnProperty(prefix = "earthchen.security.oauth2",
name = "storeType",
havingValue = "jwt",
matchIfMissing = true)
public static class JwtTokenConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
return converter;
}
@Bean
@ConditionalOnBean(TokenEnhancer.class)
public TokenEnhancer jwtTokenEnhancer(){
return new ImoocJwtTokenEnhancer();
}
}
}
jwt的配置
- 依旧在TokenStoreConfig里
/**
* jwt的配置
*
* 使用jwt时的配置,默认生效
*/
@Configuration
@ConditionalOnProperty(prefix = "earthchen.security.oauth2",
name = "storeType",
havingValue = "jwt",
matchIfMissing = true)
public static class JwtTokenConfig {
@Autowired
private SecurityProperties securityProperties;
//负责token的存储,并不管理token的生成
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
// 这个也是配置在端点里
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
/ /设置jwt的密签 converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
return converter;
}
@Bean
@ConditionalOnBean(TokenEnhancer.class)
public TokenEnhancer jwtTokenEnhancer(){
return new ImoocJwtTokenEnhancer();
}
}
2.在jwt包里定义TokenEnhnhancer
/**
* 自定义jwt token内的数据
* jwt token 增强器
*/
public class ImoocJwtTokenEnhancer implements TokenEnhancer {
/**
* 实现方法
* @param accessToken
* @param authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
// 往token写入的内容
info.put("company", "earthchen");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
- 同样将其配置在端点
// 如果jwt转换器和jwt增强器都存在
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
-
可以通过这个接口访问用户信息,但是并不能拿到增强的信息。
image.png
- 拿到增强信息
-
添加依赖
image.png
image.png
-
令牌的刷新
获取令牌同时发送refresh_token,刷新令牌,在用户无感知的情况下刷新access_token
依旧配置clientId等,地址不变,但是grantType要变成refreshToken,此时即可刷新token
网友评论