用户名密码登录获取token
WhaleAuthenticationSuccessHandler
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) };
}
}
WhaleResourceServerConfig
参考 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
测试用手机验证获取token
WhaleResourceServerConfig
这个不能配置,因为如果加上了验证码校验的配置,表单获取token的时候也就需要验证码了,
继而不能请求发送手机短信的token,后面再说吧
解决
ValidateCodeFilter 注释掉表单登录的验证码验证
// urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);
ok
拿着token请求发送手机短信验证码的接口
上次的token9995dfa3-f3c5-441d-9e3a-032ab62eec02
只需要在请求头上加上token就可以请求资源
postman用手机短信码去获取token及问题
我们的postman是模拟浏览器请求,会带上cookie,cookie里面又还有session id 可以找到session
但是如果我们用代码去发送请求,它是没有cookie也没有session 所有 会报一个短信验证码不存在的错误
因为我们的验证码是存在session里面的
所有app中我们不能用session去存储手机验证码
重构验证码存储逻辑
@Autowired
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
验证码存储 ValidateCodeRepository
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);
}
RedisValidateCodeRepository
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());
}
}
SessionValidateCodeRepository
浏览器端还是存到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());
}
}
`
AbstractValidateCodeProcessor 更换验证码存储接口
引入
@Autowired
private ValidateCodeRepository validateCodeRepository;
来对应操作验证码
在依赖app的时候就会找到我们的RedisValidateCodeRepository 的bean
redis配置
## Redis服务器地址
spring.redis.host=192.168.85.134
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=123456
测试
测试成功