1.用户的登录
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
1.项目中用户的登录:
1、客户端请求认证服务进行认证。
2、认证服务认证通过向浏览器cookie写入token(身份令牌)
- 认证服务请求用户中心查询用户信息。
- 认证服务请求Spring Security申请令牌。
- 认证服务将token(身份令牌)和jwt令牌存储至redis中。
- 认证服务向cookie写入 token(身份令牌)
2.用户在前端的显示:
前端携带身份(token)请求认证服务获取jredis中存储的wt令牌
- 前端获取到jwt令牌并存储在sessionStorage。
- 前端从jwt令牌中解析中用户信息并显示在页面
3.用户的退出
- 删除redis中的token
- 删除cookie中的token。
1.1.首先申请到令牌
申请令牌的URL固定为:/oauth/token。这里使用了ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, httpEntity, Map.class);
这种远程调用eureka中自己的服务,申请到令牌。
申请获取令牌,首先需要在自定义的UserDetailsService的实现类loadUserByUsername()方法中校验的是Basic Auth 中的Client_id,Client_security。通过之后然后在相同的类,方法中校验用户名和密码。两个都通过之后,才会生成令牌。具体的步骤在上篇文章中有介绍。
1.2将令牌存储到redis,其中身份令牌存储到Cookie中。
这个是总的代码。
public AuthToken login(String username, String password, String clientId, String clientSecret) {
//1.首先申请到令牌
AuthToken authToken = applyToken(username, password, clientId, clientSecret);
if(authToken==null){
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
//用户身份令牌
String access_token = authToken.getAccess_token();
String jsonString = JSON.toJSONString(authToken);
//将令牌存储到redis
boolean result = this.saveToken(access_token, jsonString, tokenValiditySeconds);
if (!result) {
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
1.1.首先申请到令牌
//申请令牌
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
//从eureka中获取认证服务的地址(因为spring security在认证服务中)
//从eureka中获取认证服务的一个实例。
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
//此地址就是http://ip:port
URI uri = serviceInstance.getUri();
//令牌申请的地址 http://localhost:40400/auth/oauth/token
String authUrl = uri+ "/auth/oauth/token";
//定义header
LinkedMultiValueMap<String, String> header = new LinkedMultiValueMap<>();
String httpBasic = getHttpBasic("clientId", "clientSecret");
header.add("Authorization",httpBasic);
//定义body
LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username",username);
body.add("password",password);
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(body, header);
//String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables
//设置restTemplate远程调用时候,对400和401不让报错,正确返回数据
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if(response.getRawStatusCode()!=400 && response.getRawStatusCode()!=401){
super.handleError(response);
}
}
});
ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, httpEntity, Map.class);
//申请令牌消息
Map bodyMap = exchange.getBody();
if (bodyMap == null ||
bodyMap.get("access_token") == null ||
bodyMap.get("refresh_token") == null ||
bodyMap.get("jti") == null) {
System.out.println(bodyMap.get("error_description"));
//解析spring security返回的错误信息
if (bodyMap != null && bodyMap.get("error_description") != null) {
String error_description = (String) bodyMap.get("error_description");
if (error_description.indexOf("UserDetailsService returned null") >= 0) {
ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
} else if (error_description.indexOf("坏的凭证") >= 0) {
ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
}
}
return null;
}
AuthToken authToken = new AuthToken();
authToken.setAccess_token((String) bodyMap.get("jti"));//用户身份令牌
authToken.setRefresh_token((String) bodyMap.get("refresh_token"));//刷新令牌
authToken.setJwt_token((String) bodyMap.get("access_token"));//jwt令牌
return authToken;
}
private String getHttpBasic(String clientId,String clientSecret){
String string = clientId+":"+clientSecret;
byte[] encode = Base64Utils.encode(string.getBytes());
return "Basic"+new String(encode);
}
public AuthToken getUserToken(String token){
String key ="user_token:"+token;
String value = stringRedisTemplate.opsForValue().get(key);
//转成对象
try {
AuthToken authToken = JSON.parseObject(value, AuthToken.class);
return authToken;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
1.2将令牌存储到redis
private boolean saveToken(String access_token,String content,long ttl){
String key = "user_token:"+access_token;
stringRedisTemplate.boundValueOps(access_token).set(content,ttl, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
return expire>0;
}
将令牌存储到Cookie中.
//将令牌存储到cookie
private void saveCookie(String token){
//RequestContextHolder顾名思义,持有上下文的Request容器
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
如果是删除Cookie中的内容的话,只需要把cookieMaxAge修改为0,即可。
CookieUtil.addCookie(response,cookieDomain,"/","uid",token,cookieMaxAge,false);
}
用户的授权
springcecuriy中本身提供的User对象中就含有authrities属性,我们通过继承它实现自己的类。然后赋予相应的权限。