乐优商城项目总结——11.5 授权中心(JWT和RSA实现)

创建授权中心

上一篇介绍了JWT和RSA,这里就用这两种技术实现授权中心。
先在网关(zuul)中配置一下,前面几篇没讲到
yml:
最后一段是授权中心的路由。

zuul:
  prefix: /api # 添加路由前缀,是全局的前缀,请求都得加上api
  routes:
    item-service: /item/**  #可以省略,但是默认是/item-service/**,这样改下路径,前面是eureka的服务id,后面是映射路径
    search-service: /search/**
    user-service: /user/**
    auth-service: /auth/**

授权中心也是一个聚合工程,common里放的是工具类。

在这里插入图片描述

common里的类

首先是用户的实体类,用来存登录用户的信息,这些数据以后放到载荷里。
注解使用的是lombok:
set,get 方法
无参构造
全参构造

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private Long id;
    private String username;
}

然后就是util包,太多了,略。

具体业务

流程图:
在这里插入图片描述
也就是说在授权中心给权限,在网关鉴权,在有的微服务也要鉴权。

1,先从授权中心讲:

授权中心要在yml中配置jwt的相关配置。
yml:
登录校验的密钥(secret)自己设置,我胡乱打的。

ly:
  jwt:
    secret: afjdkjgkdsglkas # 登录校验的密钥
    pubKeyPath: E:/Java_IOTest/leyoumiyao/rsa.pub # 公钥地址
    priKeyPath: E:/Java_IOTest/leyoumiyao/rsa.pri # 私钥地址
    expire: 30 # 过期时间,单位分钟
    cookieName: LY_TOKEN
    cookieMaxAge: 1800  #cookie过期时间

controller:
登录的时候写一个方法给用户授权,生成jwt类型的token。
在进行一些操作的时候进行校验用户的登陆时间(在controller就完成了)。

@Controller
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {
    @Autowired
    private AuthService authService;

    @Autowired
    private JwtProperties prop;

    /**
     * 登录授权
     *
     * @param username
     * @param password
     * @return
     */
    @PostMapping("accredit")
    public ResponseEntity<Void> authentication(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            HttpServletRequest request,
            HttpServletResponse response) {
        // 登录校验
        String token = this.authService.accredit(username, password);
        if (StringUtils.isBlank(token)) {
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        // 将token写入cookie,并指定httpOnly为true,防止通过JS获取和修改
        CookieUtils.setCookie(request, response, prop.getCookieName(),
                token, prop.getCookieMaxAge(), null, true);
        return ResponseEntity.ok().build();
    }

    /**
     * 验证用户登录状态
     *
     * @param token
     * @return
     */
    @GetMapping("verify")
    public ResponseEntity<UserInfo> verify(@CookieValue("LY_TOKEN") String token
            , HttpServletRequest request, HttpServletResponse response) {
//        解析token
        try {
            UserInfo info = JwtUtils.getInfoFromToken(token, prop.getPublicKey());
//            刷新token
            String newToken = JwtUtils.generateToken(info, prop.getPrivateKey(), prop.getExpire());
//            歇入cookie中
            CookieUtils.setCookie(request, response, prop.getCookieName(),
                    newToken, prop.getCookieMaxAge(), null, true);
//            已登录,返回用户信息
            return ResponseEntity.ok(info);
        } catch (Exception e) {
//            token已过期,抛出异常
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }
}

service:

@Slf4j
@Service
@EnableConfigurationProperties(JwtProperties.class)
public class AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties jwtProperties;

    public String accredit(String username, String password) {
//        1,根据用户名和密码查询
        User user = userClient.queryUser(username, password);
//        2,判断user
        if (user == null) {
            log.info("用户信息不存在,{}", username);
            return null;
        }
//        3,jwtUtils生成jwt类型的token
        UserInfo userInfo = new UserInfo();
        userInfo.setId(user.getId());
        userInfo.setUsername(user.getUsername());
        try {
            return JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

2,再讲网关(zuul)里的校验:

在这里面yml也要进行一些配置
因为只做校验,只需要公钥就可以
有的操作不需要登录也应该让用户能操作,比如查找商品,看你个商品你还让我登录,要我我就直接不买了。所以添加白名单allowPaths。

ly:
  jwt:
    pubKeyPath: E:/Java_IOTest/leyoumiyao/rsa.pub # 公钥地址
    cookieName: LY_TOKEN
  filter:
    allowPaths:
      - /api/auth
      - /api/search
      - /api/user/register
      - /api/user/check
      - /api/user/code
      - /api/item

zuul网关过滤器里实现校验:
在这里面有四个重写方法:

  1. 第一个是过滤类型,要在里面说明什么时候过滤,是在刚进网关?进入网关之后? 指明什么时候过滤。
  2. 第二个是过滤顺序,在官方过滤器之前就-1,等等有很多类型。
  3. 第三个是要不要过滤,因为前面说了有白名单,所以在里面进行判断。
  4. 第四个是要对过滤的业务逻辑,如果不不符和要求直接拦截下来。
@Component
@EnableConfigurationProperties({JwtProperties.class, FilterProperties.class})
public class AuthFilter extends ZuulFilter {
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private FilterProperties filterProperties;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;//过滤器类型,前置过滤
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;//过滤器顺序,在官方过滤器前面,所以-1
    }

    @Override
    public boolean shouldFilter() {         //要不要过滤
//          设置白名单
//        获取上下文
        RequestContext context = RequestContext.getCurrentContext();
//        获取request
        HttpServletRequest request = context.getRequest();
//          获取请求路径
        String path = request.getRequestURI();
//          允许放行,应该返回false,因为这个方法是(是否过滤)
        return !isAllowPath(path);
    }

    private boolean isAllowPath(String path) {
        for (String allowPath : filterProperties.getAllowPaths()) {
//            判断是否允许
            if (path.startsWith(allowPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 过滤器逻辑
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() {
//        获取上下文
        RequestContext context = RequestContext.getCurrentContext();
//        获取request
        HttpServletRequest request = context.getRequest();
//        获取cookie里的token
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookieName());

//        解析token
        try {
            UserInfo user = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
        } catch (Exception e) {
//            解析token失败,未登录,拦截
            context.setSendZuulResponse(false);
//            设置状态码为403
            context.setResponseStatusCode(403);
        }
//        校验权限
        return null;
    }
}

3,最后讲如何获取JWT里的信息:

这里讲下单微服务里的获取。
需要的配置跟网关里的差不多。
yml
只需要解析,所以只用知道公钥的地址

ly:
  jwt:
    pubKeyPath: E:/Java_IOTest/leyoumiyao/rsa.pub # 公钥地址
    cookieName: LY_TOKEN

到了下单这一步,就需要获取用户的信息,这里用的是springMVC拦截器。
需要注意的是threadlocal,threadlocal是一个thread的局部变量,可以在当前线程访问资源时共享访问资源,可以把用户信息放到threadlocal里面,当线程用完后记得销毁,虽然它会自动销毁,但是用的是tomcat的线程池,线程用完是归还,但是不会销毁,所以要手动销毁。

**
 * 添加拦截器解析用户信息
 * springMVC拦截,如果只需要某一个方法,就不用实现全部三个方法,继承HandlerInterceptorAdapter就可以重写其中一个方法
 */
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class CartFilter extends HandlerInterceptorAdapter {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * threadlocal是一个thread的局部变量,可以在当前线程访问资源时共享访问资源
     */
    private static final ThreadLocal<UserInfo> THREAD_LOCAL = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        获取cookie的token
        String token = CookieUtils.getCookieValue(request, jwtProperties.getCookieName());
//        解析token,获取用户信息,因为如果没有正确的token就不会到达这里,在网关那里就已经拦截了
        UserInfo userInfo = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
        if (userInfo == null) {
            return false;
        }
//        放入到threadlocal
        THREAD_LOCAL.set(userInfo);
        return true;
    }

    /**
     * 获取userInfo的方法
     *
     * @return
     */
    public static UserInfo getUserInfo() {
        return THREAD_LOCAL.get();
    }

    /**
     * 在完成方法的时候删除线程局部变量
     * 因为:虽然他会自动删除,但是此时用的是tomcat的线程池,用完会回收但不会删除当前线程,所以要手动删除
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        THREAD_LOCAL.remove();
    }

}

有了用户信息,就该让他掏钱了。

OVER

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值