最近在重构公司用户中心,所以自己对oauth有了一点新的理解,因为公司业务形态需求使用默认的四种模式都不太合适,所以选择自定义实现token的下发,本文需要对oauth流程有一定理解可能会容易看一点,只是主要流程的部分实现。
时序图
/** * 自定义登录验证filter */ @Order(10) public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String SPRING_SECURITY_FORM_UCENTER_KEY = "userCode"; @Getter @Setter private String loginParameter = SPRING_SECURITY_FORM_UCENTER_KEY; @Getter @Setter private boolean postOnly = true; @Getter @Setter private AuthenticationEventPublisher eventPublisher; @Getter @Setter private AuthenticationEntryPoint authenticationEntryPoint; public LoginAuthenticationFilter() { super(new AntPathRequestMatcher(SecurityConstants.UCENTER_TOKEN_URL, "POST")); } @Override @SneakyThrows public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String login = obtainLogin(request); if (login == null) { MessageSourceUtil messageSourceUtil = SpringUtils.getBean("messageSourceUtil"); throw BusinessException.get(ExceptionConstant.SYSTEM_ERROR, messageSourceUtil.getMessage(I18nConstant.SystemMessage.SYSTEM_CHECKCODE_EMPTY)); } login = login.trim(); LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(login, request.getParameter("password")); setDetails(request, loginAuthenticationToken); Authentication authResult = null; try { authResult = this.getAuthenticationManager().authenticate(loginAuthenticationToken); logger.debug("Authentication success: " + authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } catch (Exception failed) { SecurityContextHolder.clearContext(); logger.debug("Authentication request failed: " + failed); eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A")); try { authenticationEntryPoint.commence(request, response, new UsernameNotFoundException(failed.getMessage(), failed)); } catch (Exception e) { logger.error("authenticationEntryPoint handle error:{}", failed); } } return authResult; } private String obtainMobile(HttpServletRequest request) { return request.getParameter(loginParameter); } private void setDetails(HttpServletRequest request, LoginAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } }
/** * 自定义登录校验逻辑 */ @Slf4j public class LoginAuthenticationProvider implements AuthenticationProvider { @Autowired MessageSourceUtil messageSourceUtil; private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private UserDetailsChecker detailsChecker = new PreAuthenticationChecks(); @Getter @Setter private SysUserService sysUserService; @Override @SneakyThrows public Authentication authenticate(Authentication authentication) { LoginAuthenticationToken loginAuthenticationToken = (LoginAuthenticationToken) authentication; String userCode = loginAuthenticationToken.getPrincipal().toString(); UserDetails userDetails = sysUserService.loadUserByUCenter(userCode); if (userDetails == null) { log.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account")); } String frontPassword = ((LoginAuthenticationToken) authentication).getPassword(); if (StringUtils.isNotBlank(frontPassword)) { String dePassword = RSAUtils.decrypt(frontPassword); if (StringUtils.isNotBlank(dePassword)) { frontPassword = dePassword; } else { throw BusinessException.get(ExceptionConstant.SYSTEM_ERROR, messageSourceUtil.getMessage(I18nConstant.SystemMessage.PASSWORD_EMPTY)); } } BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); if (!passwordEncoder.matches(frontPassword, userDetails.getPassword())) { throw BusinessException.get(ExceptionConstant.SYSTEM_ERROR, messageSourceUtil.getMessage(I18nConstant.SystemMessage.USERCODE_OR_PASSWORD_ERROR)); } // 检查账号状态 detailsChecker.check(userDetails); LoginAuthenticationToken authenticationToken = new LoginAuthenticationToken(userDetails, userDetails.getAuthorities()); authenticationToken.setDetails(loginAuthenticationToken.getDetails()); return authenticationToken; } @Override public boolean supports(Class> authentication) { return LoginAuthenticationToken.class.isAssignableFrom(authentication); } }
/** * 自定义登录配置入口 */ @Getter @Setter public class LoginSecurityConfigurer extends SecurityConfigurerAdapter, HttpSecurity> { @Autowired private ObjectMapper objectMapper; @Autowired private AuthenticationEventPublisher defaultAuthenticationEventPublisher; @Autowired private AuthenticationSuccessHandler loginSuccessHandler; @Autowired private SysUserService sysUserService; @Override @SneakyThrows public void configure(HttpSecurity http) { LoginAuthenticationFilter loginAuthenticationFilter = new LoginAuthenticationFilter(); loginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); loginAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler); loginAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher); loginAuthenticationFilter.setAuthenticationEntryPoint(new ResourceAuthExceptionEntryPoint(objectMapper)); LoginAuthenticationProvider loginAuthenticationProvider = new LoginAuthenticationProvider(); loginAuthenticationProvider.setSysUserService(sysUserService); http.authenticationProvider(loginAuthenticationProvider).addFilterAfter(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
/** * 认证成功事件监听器 */ @Component public class AuthenticationSuccessEventListener implements ApplicationListener{ @Autowired(required = false) private AuthenticationSuccessHandler successHandler; /** * Handle an application event. * * @param event the event to respond to */ @Override public void onApplicationEvent(AuthenticationSuccessEvent event) { Authentication authentication = (Authentication) event.getSource(); if (successHandler != null && isUserAuthentication(authentication)) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); HttpServletResponse response = requestAttributes.getResponse(); successHandler.handle(authentication, request, response); } } private boolean isUserAuthentication(Authentication authentication) { return authentication.getPrincipal() instanceof ImileUser || CollUtil.isNotEmpty(authentication.getAuthorities()); } }
/** * 自定义登录成功,返回oauth token */ @Slf4j public class LoginSuccessHandler implements AuthenticationSuccessHandler { private static final String BASIC_ = "Basic "; @Autowired RedisHolderService redisHolderService; @Autowired private ObjectMapper objectMapper; @Autowired private PasswordEncoder passwordEncoder; @Autowired private ClientDetailsService clientDetailsService; @Lazy @Autowired private AuthorizationServerTokenServices defaultAuthorizationServerTokenServices; /** * Called when a user has been successfully authenticated. 调用spring security oauth API * 生成 oAuth2AccessToken * * @param request the request which caused the successful authentication * @param response the response * @param authentication the Authentication object which was created during */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { String header = request.getHeader(HttpHeaders.AUTHORIZATION); try { String[] tokens = AuthUtils.extractAndDecodeHeader(header); assert tokens.length == 2; String clientId = tokens[0]; ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); // 校验secret if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret())) { throw new InvalidClientException("Given client ID does not match authenticated client"); } TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, clientDetails.getScope(), "ucenter"); // 校验scope new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails); OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication); OAuth2AccessToken oAuth2AccessToken = defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication); String token = oAuth2AccessToken.getValue(); log.info("获取token 成功:{}", oAuth2AccessToken.getValue()); //写入redis String locale = StringUtils.isEmpty(request.getParameter("lang")) ? Locale.CHINA.toString() : request.getParameter("lang"); String timeZone = StringUtils.isEmpty(request.getParameter("timeZone")) ? DEFAULT_TIME_ZONE : request.getParameter("timeZone"); response.setCharacterEncoding(CharsetUtil.UTF_8); response.setContentType(MediaType.APPLICATION_JSON_VALUE); PrintWriter printWriter = response.getWriter(); printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken)); } catch (IOException e) { throw new BadCredentialsException("Failed to decode basic authentication token"); } }