shiro整合jwt+redis

   前两篇文章我们整合了shiro+jwt无状态权限验证,这样只把tocken存储在前端,后端只验证Tocken是否合法。
   考虑一个问题就是我们在设置了token过期时间之后会使前端用户退出在APP端的话很不友好,而且也不是很安全。
   两种业务模式仅供参考:
   1.BS架构时 我需要前端在国半小时自动退出,那么我们可以直接使用tocken过期自动退出。
   2.在app端我不想让用户经常退出,需要让他自己手动退出,那么问题来了,JWT是不能手动注销的,我们就需要用到了redis做存储,设置redis的失效时间长于自己定义的jwt的tocken过期时间,每次登陆时先清空该用户的tocken 然后创建新的tocken存储在redis里面,在Realm类里面验证自定义的tocken是否过期,过期之后去查找redis里面的tocken进行tocken验证。验证成功之后在创建一个新的token返回前端 重新给redis的token赋值,这样就不会出现 因为token过期而实效的问题了,当需要注销登录时直接清空前端token值和redis里面的token值就可以了。
  问题来了:假设redis里面的token过期之后就没有新的tocken返回前端了 ,所以解决这个问题就用到了数据库,当redis里面的tocken过期之后去检查数据库然后更新数据库新的tocken和更新redis里面的新的tocken值返回给前端。

业务一: Realm类

package eurekaclientone.demo.shiro;


import eurekaclientone.demo.daoImpl.loginImpl;
import eurekaclientone.demo.jwt.jwtToken;
import eurekaclientone.demo.utils.RedisUtil;
import eurekaclientone.demo.utils.jwtUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.Set;
@Component
public class CustomRealm extends AuthorizingRealm {
    HttpServletRequest request;
    HttpServletResponse response;
    private loginImpl logins;
    @Autowired
    private void setUserMapper(loginImpl logins) {
        this.logins = logins;
    }
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private eurekaclientone.demo.daoImpl.tockenImpl tockenImpl;

    /**
     * 必须重写此方法,不然会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof jwtToken;
    }


    /**
     * 获取身份验证信息
     * Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
     *
     * @param authenticationToken 用户身份信息 token
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("————身份认证方法————");
        String token = (String) authenticationToken.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = jwtUtil.getUsername(token);
        //首先先去判断自动生成的token是否已经过期  如果已经过期去redis里面判断是否过期
         if(!jwtUtil.verify(token, username)){
             if(redisUtil.hasKey(username)==false){
                 //如果redis里面也是空的提示重新登录  有可能tocken过期 就在数据库里面查询
                 if(StringUtils.isBlank(tockenImpl.selectToken(username))){
                     throw new AuthenticationException("您的登录已过期,请您重新登录!");
                 }else {
                         //判断数据库
                         if(StringUtils.isNotBlank(tockenImpl.selectToken(username))){
                                 //如果数据库不是空的 判断该用户是否存在
                                 String password = logins.getPassword(username);
                                 if (password == null) {
                                     throw new AuthenticationException("该用户不存在!");
                                 }
                                 //重新生成token 放进redis 每次请求方法返回给前端重置token
                                 redisUtil.del(username);
                                 tockenImpl.deTocken(username);
                                 redisUtil.set(username,jwtUtil.createToken(username),360);
                                 tockenImpl.inTocken(username,jwtUtil.createToken(username));
                         }

                     }

                 } else {
                 if(redisUtil.get(username).toString().equals(token)) {
                     String password = logins.getPassword(username);
                     if (password == null) {
                         throw new AuthenticationException("该用户不存在!");
                     }
                 }else{
                     throw new AuthenticationException("用户认证失败");
                 }
             }
             }


        // 拿到视图解析器
           /*    if (username == null || !jwtUtil.verify(token, username)) {
                    throw new AuthenticationException("token认证失败!");
                }*/
       //如果自动生成的token不是空的 判断该用户是否存在
        String password = logins.getPassword(username);
        if (password == null) {
            throw new AuthenticationException("该用户不存在!");
        }
        return new SimpleAuthenticationInfo(token, token, "MyRealm");
        //从Shiro中获取用户
        //UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
     /* // 从数据库获取对应用户名密码的用户
        String password = logins.getPassword(token.getUsername());
        if (null == password) {
            throw new AccountException("用户名不正确");
        } else if (!password.equals(new String((char[]) token.getCredentials()))) {
            throw new AccountException("密码不正确");
        }
        return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());*/


    }


    /**
     * 获取授权信息
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("————权限认证————");
        //单独shiro认证时
      /*  String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得该用户角色
        String role = logins.getRole(username);
        //如果一个用户有多个权限应该是List接受循环遍历
        Set<String> set = new HashSet<>();
        //需要将 role 封装到 Set 作为 info.setRoles() 的参数
        set.add(role);
        //设置该用户拥有的角色
        info.setRoles(set);*/

      //shiro+jwt方式
        Set<String> roleSet = new HashSet<>();
        Set<String> permissionSet = new HashSet<>();
        //获取用户名
        String username = jwtUtil.getUsername(principalCollection.toString());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得该用户角色
        String role = logins.getRole(username);
        //每个角色拥有默认的权限
        String rolePermission = logins.getPermiss(username);
        //每个用户可以设置新的权限
        //String permission = logins.getPermission(username);
        roleSet.add(role);
        permissionSet.add(rolePermission);
        info.setRoles(roleSet);
        info.setStringPermissions(permissionSet);
        return info;
    }
}

登录接口

    @PostMapping(value = "/login")
    @ApiOperation(value = "登录接口",httpMethod = "POST")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(name = "account",required = true,type = "string"),
            @ApiImplicitParam(name = "password",required = true,type = "string")
    })
    public Result show(@RequestParam(value = "account") String account,@RequestParam(value = "password") String password){
        //单shiro
      /*  // 从SecurityUtils里边创建一个 subject
        Subject subject = SecurityUtils.getSubject();
        // 在认证提交前准备 token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(account, password);
        // 执行认证登陆
        subject.login(token);
        //根据权限,指定返回数据
        //String role = logins.getRole(account);*/
      //shiro+jwt方式
        Result result=new Result();
        //登录时把生成的token放进redis  失效时间大于token设置失效时间
        List list=new LinkedList();
        String token=jwtUtil.createToken(account);
        //先清空该用户的token
        redisUtil.del(account);
        tockenImpl.deTocken(account);
        //把新的token放进redis
        redisUtil.set(account,token,360);
        tockenImpl.inTocken(account,token);
       login login= logins.show(account,password);
        list.add(token);
        list.add(login.getAccount());
        list.add(login.getId());
        result.setList(list);
        return result;
    }

注销接口

 @GetMapping("/logout")
    @ApiOperation("注销接口")
    public Result logout(@RequestHeader(value = "Token")String token){
        Result result=new Result();
        String account=jwtUtil.getUsername(token);
        redisUtil.del(account);
        tockenImpl.deTocken(account);
        result.setMessage("注销成功");
       return result;
    }
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值