AuthenticationSuccessHandler 在成功处理器中根据请求头解析出client-id 参考 org.springframework.security.web.authentication.www.BasicAuthenticationFilter#doFilterInternal
@Component("whaleAuthenticationSuccessHandler") public class WhaleAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); // ObjectMapper spring mvc 提供的 @Autowired private ObjectMapper objectMapper; @Autowired private SecurityProperties securityProperties; @Autowired private ClientDetailsService clientDetailsService; @Autowired private AuthorizationServerTokenServices authorizationServerTokenServices; /* * (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("登录成功"); String header = request.getHeader("Authorization"); if (header == null || !header.toLowerCase().startsWith("basic ")) { // chain.doFilter(request, response); throw new UnapprovedClientAuthenticationException("请求头中无client信息"); // return; } // try { String[] tokens = extractAndDecodeHeader(header, request); assert tokens.length == 2; String username = tokens[0]; String clientId = tokens[0]; String clientSecret = tokens[1]; //拿到了clientDetails ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); //校验 if(clientDetails==null){ throw new UnapprovedClientAuthenticationException("client-id对应信息不存在:"+clientId); }else if(! StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){ throw new UnapprovedClientAuthenticationException("client-secret对应信息不匹配:"+clientSecret); } //不需要再构建authentication 传一个空的map //grantType 为四种授权模式 我们是自定义的 TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom"); OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication); OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication); response.setContentType("application/json;charset=UTF-8"); // response.getWriter().write(objectMapper.writeValueAsString(authentication)); response.getWriter().write(objectMapper.writeValueAsString(oAuth2AccessToken)); // if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ // response.setContentType("application/json;charset=UTF-8"); // response.getWriter().write(objectMapper.writeValueAsString(authentication)); // }else { // //如果配置的返回类型不是json,则调用父类方法,进行跳转 // super.onAuthenticationSuccess(request,response,authentication); // } } /** * Decodes the header into a username and password. * * @throws BadCredentialsException if the Basic header is not present or is not valid * 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, getCredentialsCharset(request)); 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) }; } }参考 BrowserSecurityConfig
@Configuration @EnableResourceServer public class WhaleResourceServerConfig extends ResourceServerConfigurerAdapter { @Autowired protected AuthenticationSuccessHandler whaleAuthenticationSuccessHandler; @Autowired protected AuthenticationFailureHandler whaleAuthenticationFailureHandler; @Autowired private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; @Autowired private SecurityProperties securityProperties; @Override public void configure(HttpSecurity http) throws Exception { super.configure(http); http.formLogin() .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL) .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM) .successHandler(whaleAuthenticationSuccessHandler) .failureHandler(whaleAuthenticationFailureHandler); http//.apply(validateCodeSecurityConfig) //.and() .apply(smsCodeAuthenticationSecurityConfig) .and() /* APP上没有rember的概念 .rememberMe() .tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSecodes())*/ /* .userDetailsService(userDetailsService) .and() .sessionManagement() .invalidSessionStrategy(invalidSessionStrategy) .maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions()) .maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin()) .expiredSessionStrategy(sessionInformationExpiredStrategy)*/ // .invalidSessionUrl("/session/invalid") // .maximumSessions(1)//为1 后面登录的session会把前面的登录的session失效掉 // .maxSessionsPreventsLogin(true)//当session数量达到最大时 阻止后面的登录 // .expiredSessionStrategy(new MyExpiredSessionStrategy())//并发登录导致超时的处理策略 // .and() // .and() // .logout() // .logoutUrl("/signOut") // .logoutSuccessUrl("/logOut.html") // .logoutSuccessHandler(LogoutSuccessHandler) //Handler和Url是互斥的 // .deleteCookies("JESSIONID")//清除cookie中的 当前session id // .and() .authorizeRequests() .antMatchers( SecurityConstants.DEFAULT_UNAUTHENTICATION_URL, SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, securityProperties.getBrowser().getLoginPage(), SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*", securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".json", securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".html", "/session/invalid") .permitAll() .anyRequest() .authenticated() .and() .cors().disable().csrf().disable();// 禁用跨站攻击 } }模拟表单登录获取token
拿着token请求资源 直接浏览器访问会报错
这个不能配置,因为如果加上了验证码校验的配置,表单获取token的时候也就需要验证码了, 继而不能请求发送手机短信的token,后面再说吧 解决 ValidateCodeFilter 注释掉表单登录的验证码验证
// urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);ok
上次的token9995dfa3-f3c5-441d-9e3a-032ab62eec02 只需要在请求头上加上token就可以请求资源
我们的postman是模拟浏览器请求,会带上cookie,cookie里面又还有session id 可以找到session 但是如果我们用代码去发送请求,它是没有cookie也没有session 所有 会报一个短信验证码不存在的错误 因为我们的验证码是存在session里面的
所有app中我们不能用session去存储手机验证码
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
ValidateCodeRepository
public interface ValidateCodeRepository { /** * 保存验证码 * @param request * @param validateCode * @param validateCodeType */ void save(ServletWebRequest request ,ValidateCode validateCode,ValidateCodeType validateCodeType); /** * 获取验证码 * @param request * @param validateCodeType * @return */ ValidateCode get(ServletWebRequest request,ValidateCodeType validateCodeType); /** * 移除验证码 * @param request * @param validateCodeType * @return */ void remove(ServletWebRequest request,ValidateCodeType validateCodeType); }app端我们把验证码存到redis里面
@Component public class RedisValidateCodeRepository implements ValidateCodeRepository { @Autowired private RedisTemplate<Object,Object> redisTemplate; /** * 保存验证码 * * @param request * @param validateCode * @param validateCodeType */ @Override public void save(ServletWebRequest request, ValidateCode validateCode, ValidateCodeType validateCodeType) { //存redis 30分钟超时时间 redisTemplate.opsForValue().set(buildKey(request,validateCodeType),validateCode,30,TimeUnit.MINUTES); } /** * 获取验证码 * @param request * @param validateCodeType * @return */ @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) { Object value = redisTemplate.opsForValue().get(buildKey(request, validateCodeType)); if(value==null){ return null; } return (ValidateCode) value; } /** * 移除验证码 * * @param request * @param validateCodeType * @return */ @Override public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) { redisTemplate.delete(buildKey(request,validateCodeType)); } private String buildKey(ServletWebRequest request, ValidateCodeType validateCodeType) { String deviceId = request.getHeader("deviceId"); if(StringUtils.isBlank(deviceId)){ throw new ValidateCodeException("请在请求头中携带deviceId参数"); } return "code:"+validateCodeType.toString().toLowerCase()+":"+deviceId; } /** * 根据请求的url获取校验码的类型 * * @param request * @return */ private ValidateCodeType getValidateCodeType(ServletWebRequest request) { String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor"); return ValidateCodeType.valueOf(type.toUpperCase()); } }浏览器端还是存到session中国
`@Component public class SessionValidateCodeRepository implements ValidateCodeRepository { /** * 操作session的工具类 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); /** * 保存验证码 * * @param request * @param validateCode * @param validateCodeType */ @Override public void save(ServletWebRequest request, ValidateCode validateCode, ValidateCodeType validateCodeType) { ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime()); sessionStrategy.setAttribute(request, getSessionKey(request), code); } /** * 获取验证码 * * @param request * @param validateCodeType * @return */ @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) { String sessionKey = getSessionKey(request); ValidateCode codeInSession = (ValidateCode) sessionStrategy.getAttribute(request, sessionKey); return codeInSession; } /** * 移除验证码 * * @param request * @param validateCodeType * @return */ @Override public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) { sessionStrategy.removeAttribute(request,getSessionKey(request)); } /** * 构建验证码放入session时的key * * @param request * @return */ private String getSessionKey(ServletWebRequest request) { // return SESSION_KEY_PREFIX + getProcessorType(request); return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase(); } /** * 根据请求的url获取校验码的类型 * * @param request * @return */ private ValidateCodeType getValidateCodeType(ServletWebRequest request) { String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor"); return ValidateCodeType.valueOf(type.toUpperCase()); } }`
引入
@Autowired private ValidateCodeRepository validateCodeRepository;来对应操作验证码
在依赖app的时候就会找到我们的RedisValidateCodeRepository 的bean
测试成功