欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring-boot结合Shrio实现JWT的方法

程序员文章站 2023-12-15 18:48:52
本文介绍了spring-boot结合shrio实现jwt的方法,分享给大家,具体如下: 关于验证大致分为两个方面: 用户登录时的验证; 用户登录后每次访问...

本文介绍了spring-boot结合shrio实现jwt的方法,分享给大家,具体如下:

关于验证大致分为两个方面:

  1. 用户登录时的验证;
  2. 用户登录后每次访问时的权限认证

主要解决方法:使用自定义的shiro filter

项目搭建:

这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google。

pom.mx引入相关jar包

 <!-- shiro 权限管理 -->
  <dependency>
    <groupid>org.apache.shiro</groupid>
    <artifactid>shiro-spring</artifactid>
    <version>${shiro.version}</version>
  </dependency>
  <dependency>
    <groupid>org.apache.shiro</groupid>
    <artifactid>shiro-core</artifactid>
    <version>${shiro.version}</version>
  </dependency>
 <!-- jwt -->
   <dependency>
    <groupid>io.jsonwebtoken</groupid>
    <artifactid>jjwt</artifactid>
    <version>0.9.0</version>
  </dependency>

shrio 的相关配置

划重点!!自定义了一个filter

filtermap.put("jwtfilter", new jwtfilter());
@configuration
public class shiroconfig {
  @bean
  public shirofilterfactorybean getshirofilterfactorybean(securitymanager securitymanager) {
    shirofilterfactorybean shirofilterfactorybean = new shirofilterfactorybean();
    shirofilterfactorybean.setsecuritymanager(securitymanager);
    // 添加自己的过滤器并且取名为jwtfilter
    map<string, filter> filtermap = new hashmap<>();
    filtermap.put("jwtfilter", new jwtfilter());
    shirofilterfactorybean.setfilters(filtermap);
    /*
     * 自定义url规则
     * http://shiro.apache.org/web.html#urls-
     */
    map<string, string> filterchaindefinitionmap = shirofilterfactorybean.getfilterchaindefinitionmap();
    filterchaindefinitionmap.put("/**", "jwtfilter");
    shirofilterfactorybean.setfilterchaindefinitionmap(filterchaindefinitionmap);
    return shirofilterfactorybean;
  }


  /**
   * securitymanager 不用直接注入shirodbrealm,可能会导致事务失效
   * 解决方法见 handlecontextrefresh
   * http://www.debugrun.com/a/nks9ejq.html
   */
  @bean("securitymanager")
  public defaultwebsecuritymanager securitymanager(tokenrealm tokenrealm) {
    defaultwebsecuritymanager manager = new defaultwebsecuritymanager();
    manager.setrealm(tokenrealm);
    /*
     * 关闭shiro自带的session,详情见文档
     * http://shiro.apache.org/session-management.html#sessionmanagement-statelessapplications%28sessionless%29
     */
    defaultsubjectdao subjectdao = new defaultsubjectdao();
    defaultsessionstorageevaluator defaultsessionstorageevaluator = new defaultsessionstorageevaluator();
    defaultsessionstorageevaluator.setsessionstorageenabled(false);
    subjectdao.setsessionstorageevaluator(defaultsessionstorageevaluator);
    manager.setsubjectdao(subjectdao);
    return manager;
  }

  @bean
  public lifecyclebeanpostprocessor lifecyclebeanpostprocessor() {
    return new lifecyclebeanpostprocessor();
  }

  @bean(name = "tokenrealm")
  @dependson("lifecyclebeanpostprocessor")
  public tokenrealm tokenrealm() {
    return new tokenrealm();
  }

  @bean
  @dependson("lifecyclebeanpostprocessor")
  public defaultadvisorautoproxycreator defaultadvisorautoproxycreator() {
    defaultadvisorautoproxycreator defaultadvisorautoproxycreator = new defaultadvisorautoproxycreator();
    // 强制使用cglib,防止重复代理和可能引起代理出错的问题
    // https://zhuanlan.zhihu.com/p/29161098
    defaultadvisorautoproxycreator.setproxytargetclass(true);
    return defaultadvisorautoproxycreator;
  }

  @bean
  public authorizationattributesourceadvisor getauthorizationattributesourceadvisor(securitymanager securitymanager) {
    authorizationattributesourceadvisor authorizationattributesourceadvisor = new authorizationattributesourceadvisor();
    authorizationattributesourceadvisor.setsecuritymanager(securitymanager);
    return new authorizationattributesourceadvisor();
  }
}

自定义shrio filter

执行顺序:prehandle -> dofilterinternal -> executelogin -> onloginsuccess

主要判断是不是登录请求的是 dofilterinternal

public class jwtfilter extends basichttpauthenticationfilter {
  /**
   * 自定义执行登录的方法
   */
  @override
  protected boolean executelogin(servletrequest request, servletresponse response) throws ioexception {
    httpservletrequest httpservletrequest = (httpservletrequest) request;
    usernamepasswordtoken usernamepasswordtoken = json.parseobject(httpservletrequest.getinputstream(), usernamepasswordtoken.class);
    // 提交给realm进行登入,如果错误他会抛出异常并被捕获
    subject subject = this.getsubject(request, response);
    subject.login(usernamepasswordtoken);
    return this.onloginsuccess(usernamepasswordtoken, subject, request, response);
    //错误抛出异常
  }

  /**
   * 最先执行的方法
   */
  @override
  protected boolean prehandle(servletrequest request, servletresponse response) throws exception {
    return super.prehandle(request, response);
  }

  /**
   * 登录成功后登录的操作
   * 加上jwt 的header
   */
  @override
  protected boolean onloginsuccess(authenticationtoken token, subject subject, servletrequest request, servletresponse response) {
    httpservletresponse httpservletresponse = (httpservletresponse) response;
    string jwttoken = jwts.builder()
        .setid(token.getprincipal().tostring())
        .setexpiration(datetime.now().plusminutes(30).todate())
        .signwith(signaturealgorithm.hs256, jwtcost.signaturekey)
        .compact();
    httpservletresponse.addheader(authorization_header, jwttoken);
    return true;
  }

  /**
   * 登录以及校验的主要流程
   * 判断是否是登录,或者是登陆后普通的一次请求
   */
  @override
  public void dofilterinternal(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception {
    httpservletrequest httpservletrequest = (httpservletrequest) servletrequest;
    httpservletresponse httpservletresponse = (httpservletresponse) servletresponse;
    string servletpath = httpservletrequest.getservletpath();
    if (stringutils.equals(servletpath, "/login")) {
      //执行登录
      this.executelogin(servletrequest, servletresponse);
    } else {
      string authenticationheader = httpservletrequest.getheader(authorization_header);
      if (stringutils.isnotempty(authenticationheader)) {

        claims body = jwts.parser()
            .setsigningkey(jwtcost.signaturekey)
            .parseclaimsjws(authenticationheader)
            .getbody();
        if (body != null) {
          //更新token
          body.setexpiration(datetime.now().plusminutes(30).todate());
          string updatetoken = jwts.builder().setclaims(body).compact();
          httpservletresponse.addheader(authorization_header, updatetoken);

          //添加用户凭证
          principalcollection principals = new simpleprincipalcollection(body.getid(), jwtcost.usernamepasswordrealm);//拼装shiro用户信息
          websubject.builder builder = new websubject.builder(servletrequest, servletresponse);
          builder.principals(principals);
          builder.authenticated(true);
          builder.sessioncreationenabled(false);
          websubject subject = builder.buildwebsubject();
          //塞入容器,统一调用
          threadcontext.bind(subject);
          filterchain.dofilter(httpservletrequest, httpservletresponse);
        }
      } else {
        httpservletresponse.setstatus(httpstatus.forbidden.value());
      }
    }
  }
}

登录失败处理

处理shrio异常

@restcontrolleradvice
public class globalcontrollerexceptionhandler {
  @exceptionhandler(value = exception.class)
  public object allexceptionhandler(httpservletrequest request, httpservletresponse response, exception exception) {
    string message = exception.getcause().getmessage();
    logutil.error(message);
    return new resultinfo(exception.getclass().getname(), message);
  }

  /*=========== shiro 异常拦截==============*/

  @exceptionhandler(value = incorrectcredentialsexception.class)
  public string incorrectcredentialsexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "incorrectcredentialsexception";
  }

  @exceptionhandler(value = unknownaccountexception.class)
  public string unknownaccountexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "unknownaccountexception";
  }

  @exceptionhandler(value = lockedaccountexception.class)
  public string lockedaccountexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "lockedaccountexception";
  }

  @exceptionhandler(value = excessiveattemptsexception.class)
  public string excessiveattemptsexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "excessiveattemptsexception";
  }

  @exceptionhandler(value = authenticationexception.class)
  public string authenticationexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "authenticationexception";
  }

  @exceptionhandler(value = unauthorizedexception.class)
  public string unauthorizedexception(httpservletrequest request, httpservletresponse response, exception exception) {
    response.setstatus(httpstatus.forbidden.value());
    return "unauthorizedexception";
  }
}

处理jwt异常

这是个坑,因为是在filter内发生的异常,@exceptionhandler是截获不到的。

/**
 * 截获spring boot error页面
 */
@restcontroller
public class globalexceptionhandler implements errorcontroller {
  @override
  public string geterrorpath() {
    return "/error";
  }

  @requestmapping(value = "/error")
  public object error(httpservletrequest request, httpservletresponse response) throws exception {
    // 错误处理逻辑
    exception exception = (exception) request.getattribute("javax.servlet.error.exception");
    throwable cause = exception.getcause();
    if (cause instanceof expiredjwtexception) {
      response.setstatus(httpstatus.gateway_timeout.value());
      return new resultinfo("expiredjwtexception", cause.getmessage());
    }
    if (cause instanceof malformedjwtexception) {
      response.setstatus(httpstatus.forbidden.value());
      return new resultinfo("malformedjwtexception", cause.getmessage());
    }
    return new resultinfo(cause.getcause().getmessage(), cause.getmessage());
  }
}

关于权限等授权信息,可以直接放到redis中实现缓存。我认为也是不错的。

源码奉上::温馨提示:平时测试代码可能比较乱。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: