jwt结合shiro实现认证和权限控制,非常详细

前期准备

jwt,我的理解就是可以进行客户端与服务端之间验证的一种技术,取代了之前使用Session来验证的不安全性。为什么不适用Session?

原理是,登录之后客户端和服务端各自保存一个相应的SessionId,每次客户端发起请求的时候就得携带这个SessionId来进行比对

  1. Session在用户请求量大的时候服务器开销太大了
  2. Session不利于搭建服务器的集群(也就是必须访问原本的那个服务器才能获取对应的SessionId)
  3. 小程序,APP不适用session,对于微信小程序,request请求每次都会先请求微信的服务器,再由微信的服务器去访问我们的后端服务器。这样子就不能识别出是哪台浏览器发送的请求。

1.首先,登录的话,不能在通过shiro自己的方法去验证了,因为我们自定义的token需要存储用户使用的token,所以登录的密码验证就不能通过shiro进行验证,而需要我们自己去验证密码的准确性,在登录方法里面可以,在realm认证方法里面也可以.
2.然后,基于token进行权限验证的话,我们请求所有需要认证的接口时候请求头里必须携带token,然后后端进行token认证,判断token是否合法是否过期等等…
3.token的刷新,可以自定义返回code,返回新的token,来进行token刷新工作.
4.token缓存在redis中,可以实现集群token的共享.可以使token的过期删除交给redis

在这里我先不涉及redis,单纯的jwt结合shiro,所以也没有做token的过期处理,登出处理,即使你使用shiro的logout方法登出,这里的token依然没有失效

由于使用jwt是无状态的,不需要使用session,所以需要把session关闭掉。具体的编码过程如下。

个人理解:

  1. jwt负责生成token,取代shiro原生的UsernamePasswordToken;shiro负责认证和权限的校验
  2. 登录逻辑沿用jwt的登录逻辑,即登录时不需要调用shiro的subject.login()方法,只需要校验用户名和密码,然后返回token即可。到了需要进行权限认证时在执行login方法,这里使用的是jwtFilter来进行拦截。

这个工作的流程有,登录,权限控制:

  1. 登录,登录还是做简单的接受请求传递过来的参数,然后和数据库对比,是否一致,一致的话则通过登录,使用jwt生成token,不经过shiro的处理,因为被jwtFilter拦截了。
  2. 认证,认证的话先是被jwtFilter拦截,然后验证token,无异常就继续进入到shiro
    • 如果是只要拥有登录权限的话,那么就经过认证方面就可以了
    • 如果是要控制权限的话,那么就要先认证再授权

有兴趣看源码的话可以去我的项目地址clone下来:

项目地址:https://github.com/HTBWell/shiro-jwt.git

1. 导入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

2. 编写JwtUtil类

JwtUtil类是用来生成token和验校验解码token的。

步骤:

  1. 设置密钥和token的有效时间
  2. 生成token
  3. 校验token
  4. 获取token的信息
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.domain.User;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {
   
    //token有效时长
    private static final long EXPIRE=30*60*1000L;
    //token的密钥
    private static final String SECRET="jwt+shiro";


    public static String createToken(User user) throws UnsupportedEncodingException {
   
        //token过期时间
        Date date=new Date(System.currentTimeMillis()+EXPIRE);

        //jwt的header部分
        Map<String ,Object>map=new HashMap<>();
        map.put("alg","HS256");
        map.put("typ","JWT");

        //使用jwt的api生成token
        String token= JWT.create()
                .withHeader(map)
                .withClaim("username", user.getUsername())//私有声明
                .withExpiresAt(date)//过期时间
                .withIssuedAt(new Date())//签发时间
                .sign(Algorithm.HMAC256(SECRET));//签名
        return token;
    }

    //校验token的有效性,1、token的header和payload是否没改过;2、没有过期
    public static boolean verify(String token){
   
        try {
   
            //解密
            JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
   
            return false;
        }
    }


    //无需解密也可以获取token的信息
    public static String getUsername(String token){
   
        try {
   
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
   
            return null;
        }

    }
}

3. 封装token

封装token来替换Shiro原生Token,要实现AuthenticationToken接口

shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。

import org.apache.shiro.authc.AuthenticationToken;

public class JWTToken implements AuthenticationToken {
   

    private String token;

    public JWTToken(String token){
   
        this.token=token;
    }

    @Override
    public Object getPrincipal() {
   
        return token;
    }

    @Override
    public Object getCredentials() {
   
        return token;
    }
}

4. 编写JWT的过滤器

这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter,一个可以内置了可以自动登录方法的的过滤器。也可以继承AuthenticatingFilter。我这里两个都实现了,跑项目的时候只要一个就好了

这个过滤器是要注册到shiro配置里面去的,用来辅助shiro进行过滤处理。所有的请求都会到过滤器来进行处理。

我们需要重写几个方法:

  1. isAccessAllowed:是否允许访问。如果带有 token,则对 token 进行检查,否则直接通过。如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
  2. isLoginAttempt:判断用户是否想要登入。检测 header 里面是否包含 Token 字段。
  3. executeLoginexecuteLogin实际上就是先调用createToken来获取token,这里我们重写了这个方法,就不会自动去调用createToken来获取token,然后调用getSubject方法来获取当前用户再调用login方法来实现登录,这也解释了我们为什么要自定义jwtToken,因为我们不再使用Shiro默认的UsernamePasswordToken了。
  4. preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。
  • 继承BasicHttpAuthenticationFilter
import com.example.demo.shiro.JWTToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
  • 52
    点赞
  • 227
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值