摘要
SpringSecurityOAuth2中分析了SpringSecurity OAuth2的实现流程,但是默认实现了5种生产Token的方式。当我们想使用账号密码登录、手机短信登录、社交应用登录时就需要自定义。
SpringSecurityOAuth2申请Token.png
一、账号密码登陆
-
通过查看源码在TokenGranter中会生成Authentication认证信息,而在我们自定义登录时,通常会定义统一的成功处理或失败处理,在统一的成功处理中就会有Authentication认证信息,所以我们自定义登录时只需要重新构建OAuth2Required、OAuth2Authentication、OAuth2AccessToken就可以拿到Token。
自定义登录方式获取Token.png
- 在我们使用账号密码登录时,需要携带项目的id和项目的Secret,参数包含账号和密码
- 通过自定义的过滤器,验证验证码、用户名、密码是否正确,再通过登录逻辑处理会判断登录是否成功,经过一系列的验证最后会进入自定义的统一成功处理和统一的失败处理,其中成功处理中就会带有Authentication信息
- 从请求头部中拿到项目的id和secret,根据需求自定义ClientDetails、OAuth2Request、OAuth2Authentication,最后声称OAuth2AccessToken。
- App自定义成功处理器(AppAuthenticationSuccessHandler)
@Component
@Slf4j
public class AppAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RavenSecurityProperties securityProperties;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功处理器");
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无client信息");
}
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
// 根据clientId获取ClientDetails对象 --- ClientDetails为第三方应用的信息
// 现在配置在了yml文件里,真实项目中应该放在数据库里
ClientDetails clientDetails = this.clientDetailsService.loadClientByClientId(clientId);
// 对获取到的clientDetails进行校验
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
} else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
}
// 第一个参数为请求参数的一个Map集合,
// 在Spring Security OAuth的源码里要用这个Map里的用户名+密码或授权码来生成Authentication对象,
// 但我们已经获取到了Authentication对象,所以这里可以直接传一个空的Map
// 第三个参数为scope即请求的权限 ---》这里的策略是获得的ClientDetails对象里配了什么权限就给什么权限
// TODO
// 第四个参数为指定什么模式 比如密码模式为password,授权码模式为authorization_code,
// 这里我们写一个自定义模式custom
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
//获取OAuth2Request对象
//源码中是这么写的 --- todo 有兴趣的可以看一下
// OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
//new出一个OAuth2Authentication对象
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
//生成token
OAuth2AccessToken token = this.authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
//将生成的token返回
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(token));
}
/**
* Base64解码
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
}
- 资源服务器配置
/**
* 资源服务器配置
*/
@Configuration
@EnableResourceServer
public class AppResourcesServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private RavenSecurityProperties securityProperties;
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailureHandler failureHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
// 配置登录界面
String loginPage = this.securityProperties.getBrowser().getLoginPage();
int tokenTime = this.securityProperties.getBrowser().getTokenTime();
String signUpUrl = this.securityProperties.getBrowser().getSignUpUrl();
// 表单配置
// this.formAuthenticationConfig.configure(http);
http.formLogin()
.loginPage(RavenSecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(RavenSecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)
.successHandler(successHandler)
.failureHandler(failureHandler);
// 验证码配置
// http.apply(this.validateCodeSecurityConfig);
// 短信配置
// http.apply(this.mobileCodeConfig);
// 社交配置
// http.apply(this.socialConfigurer);
http.csrf().disable();
http
.authorizeRequests()
.antMatchers(
RavenSecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
RavenSecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM,
loginPage,
RavenSecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "*",
signUpUrl,
"/user/regist"
).permitAll()
.anyRequest()
.authenticated();
}
}













网友评论