乐优商城(三十一)——授权中心
目录
三、首页判断登录状态
虽然cookie已经成功写入,但是首页的顶部,登录状态依然没能判断出用户信息:
这里需要向后台发起请求,根据cookie获取当前用户的信息。
3.1 页面代码
页面的顶部已经封装为一个独立的Vue组件,在/js/pages/shortcut.js
中
在created函数中,查询用户信息:
后台:
请求已经发出,因为token在cookie中,因此本次请求肯定会携带token信息在头中。
3.2 后台实现校验用户接口
在leyou-authentication-service
中定义用户的校验接口,通过cookie获取token,然后校验通过返回用户信息。
-
请求方式:GET
-
请求路径:/verify
-
请求参数:无,但需要从cookie中获取token信息
-
返回结果:UserInfo,校验成功返回用户信息;校验失败,则返回401
代码:
/**
* 用户验证
* @param token
* @return
*/
@GetMapping("verify")
public ResponseEntity<UserInfo> verifyUser(@CookieValue("LY_TOKEN") String token){
try{
//1.从token中解析token信息
UserInfo userInfo = JwtUtils.getInfoFromToken(token,this.properties.getPublicKey());
//2.解析成功返回用户信息
return ResponseEntity.ok(userInfo);
}catch (Exception e){
e.printStackTrace();
}
//3.出现异常,相应401
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
getInfoFromToken代码:
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
* @throws Exception
*/
public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
return new UserInfo(
ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
);
}
3.3 测试
页面效果:
3.4 刷新token
每当用户在页面进行新的操作,都应该刷新token的过期时间,否则30分钟后用户的登录信息就无效了。而刷新其实就是重新生成一份token,然后写入cookie即可。
那么问题来了:怎么知道用户有操作呢?
事实上,每当用户来查询其个人信息,就证明他正在浏览网页,此时刷新cookie是比较合适的时机。因此可以对校验用户登录状态的接口进行改进,加入刷新token的逻辑。
/**
* 用户验证
* @param token
* @return
*/
@GetMapping("verify")
public ResponseEntity<UserInfo> verifyUser(@CookieValue("LY_TOKEN") String token,HttpServletRequest request,
HttpServletResponse response){
try{
//1.从token中解析token信息
UserInfo userInfo = JwtUtils.getInfoFromToken(token,this.properties.getPublicKey());
//2.解析成功要重新刷新token
token = JwtUtils.generateToken(userInfo,this.properties.getPrivateKey(),this.properties.getExpire());
//3.更新Cookie中的token
CookieUtils.setCookie(request,response,this.properties.getCookieName(),token,this.properties.getCookieMaxAge());
//4.解析成功返回用户信息
return ResponseEntity.ok(userInfo);
}catch (Exception e){
e.printStackTrace();
}
//5.出现异常,相应401
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
四、网关的登录拦截
在Zuul编写拦截器,对用户的token进行校验,如果发现未登录,则进行拦截。
4.1 引入jwt相关配置
既然是登录拦截,一定是前置拦截器,在leyou-gateway
中定义。
首先在pom.xml中,引入所需要的依赖:
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>leyou-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.leyou.auth</groupId>
<artifactId>leyou-auth-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
然后编写application.yml,添加如下内容:
leyou:
jwt:
pubKeyPath: C:\\tmp\\rsa\\rsa.pub # 公钥地址
cookieName: LY_TOKEN # cookie的名称
4.2 编写过滤逻辑
基本逻辑:
-
获取cookie中的token
-
通过JWT对token进行校验
-
通过:则放行;不通过:则重定向到登录页
代码:
package com.leyou.filter;
import com.leyou.auth.utils.JwtUtils;
import com.leyou.config.JwtProperties;
import com.leyou.utils.CookieUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @Author: 98050
* @Time: 2018-10-24 16:21
* @Feature: 登录拦截器
*/
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class LoginFilter extends ZuulFilter {
@Autowired
private JwtProperties properties;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 5;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//1.获取上下文
RequestContext context = RequestContext.getCurrentContext();
//2.获取request
HttpServletRequest request = context.getRequest();
//3.获取token
String token = CookieUtils.getCookieValue(request,this.properties.getCookieName());
//4.校验
try{
//4.1 校验通过,放行
JwtUtils.getInfoFromToken(token,this.properties.getPublicKey());
}catch (Exception e){
//4.2 校验不通过,返回403
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
}
return null;
}
}
重启,刷新页面,发现请求校验的接口也被拦截了:
证明拦截器生效了,但是,这个路径是不应该被拦截,需要白名单。
4.3 白名单
要注意,并不是所有的路径我们都需要拦截,例如:
-
登录校验接口:
/auth/**
-
注册接口:
/user/register
-
数据校验接口:
/user/check/**
-
发送验证码接口:
/user/code
-
搜索接口:
/search/**
另外,跟后台管理相关的接口,因为没有做登录和权限,因此暂时都放行,但是生产环境中要做登录校验:
-
后台商品服务:
/item/**
所以,需要在拦截时,配置一个白名单,如果在名单内,则不进行拦截。
在application.yaml
中添加规则:
leyou:
filter:
allowPaths:
- /api/auth
- /api/search
- /api/user/register
- /api/user/check
- /api/user/code
- /api/item
然后读取这些属性:
package com.leyou.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @Author: 98050
* @Time: 2018-10-24 16:55
* @Feature: 过滤白名单
*/
@ConfigurationProperties(prefix = "leyou.filter")
public class FilterProperties {
private List<String> allowPaths;
public List<String> getAllowPaths() {
return allowPaths;
}
public void setAllowPaths(List<String> allowPaths) {
this.allowPaths = allowPaths;
}
}
在过滤器中的shouldFilter方法中添加判断逻辑:
@Override
public boolean shouldFilter() {
//1.获取上下文
RequestContext context = RequestContext.getCurrentContext();
//2.获取request
HttpServletRequest request = context.getRequest();
//3.获取路径
String requestUri = request.getRequestURI();
logger.info(requestUri);
//4.判断白名单
return !isAllowPath(requestUri);
}
private boolean isAllowPath(String requestUri) {
//1.定义一个标记
boolean flag = false;
//2.遍历允许访问的路径
for (String path : this.filterProperties.getAllowPaths()){
if (requestUri.startsWith(path)){
flag = true;
break;
}
}
return flag;
}
知识点:
request.getRequestURL() 返回全路径
request.getRequestURI() 返回除去host(域名或者ip)部分的路径
request.getContextPath() 返回工程名部分,如果工程映射为/,此处返回则为空
request.getServletPath() 返回除去host和工程名部分的路径
例如:
request.getRequestURL() :http://localhost:8080/jqueryLearn/resources/request.jsp
request.getRequestURI(): /jqueryLearn/resources/request.jsp
request.getContextPath():/jqueryLearn
request.getServletPath():/resources/request.j
刷新页面:
五、退出
怎么搞?