栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot整合JWT + Shiro

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot整合JWT + Shiro

一、什么是Shiro

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
三个核心组件:Subject, SecurityManager 和 Realms

三大核心组件:

Subject:主体

主体,代表了当前的“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是主体,如第三方进程、网络爬虫、机器人等,Subject是一个抽象概念,所有的Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager,可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器

安全管理器,即所有与安全有关的操作都会与SecurityManager进行交互,是Shiro框架的核心,管理所有的Subject,类似于Spring
MVC的前端控制器DispatcherServlet;

Realm:域
Shiro从Realm中获取安全数据(比如用户、角色、权限),SecurityManager要验证用户身份,需要从Realm中获取相应的用户进行比较确定用户是否合法;验证用户角色/权限也需要从Realm获得相应数据进行比较,类似于DataSource,安全数据源;它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。

需要注意的是:Shiro本身不提供维护用户、权限,而是通过Realm让开发人员自己注入到SecurityManager,从而让SecurityManager能得到合法的用户以及权限进行判断;

二、项目代码实战 1.项目依赖

只说明Shiro和JWT的依赖SpringBoot的自己配置

        
            org.apache.shiro
            shiro-spring
            1.8.0
        


        
            com.auth0
            java-jwt
            3.2.0
        
2.JWT工具类编写

既然要使用JWT第一步我们直接先准备JWT工具类编写
博主只是根据自己的需求去传入的参数,如果项目不一样,自行更改就好了
JWT就不用多说了吧

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;


public class JwtUtils {

    
    
    private static final String SECRET = "889556654";

    public static String createToken(String username, String password) throws UnsupportedEncodingException {
        //设置token时间 三小时
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.HOUR, 3);
        Date date = nowTime.getTime();

       
        //密文生成
        String token = JWT.create()
                .withClaim("username", username)
                .withClaim("password", password)
                .withExpiresAt(date)
                .withIssuedAt(new Date())
                .sign(Algorithm.HMAC256(SECRET));
        return token;
    }

    
    public static boolean verify(String token) {
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        } catch (UnsupportedEncodingException e) {
            return false;
        }
    }

    
    

    public static String getClaim(String token, String name){
        String claim = null;
        try {
            claim =  JWT.decode(token).getClaim(name).asString();
        }catch (Exception e) {
            return "getClaimFalse";
        }
        return claim;
    }
3.编写一个JwtToken.class

编写一个JwtToken.c类 继承 AuthenticationToken 之后会用到

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.编写Realm类
package com.dfinfo.smalabelbackend.filter;

import com.dfinfo.smalabelbackend.common.util.JwtUtils;
import com.dfinfo.smalabelbackend.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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;


@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("MyRealm doGetAuthorizationInfo() 方法授权 ");
        String token = principalCollection.toString();
        String username = JwtUtils.getClaim(token,"username");
        if (StringUtils.isBlank(username)) {
            throw new AuthenticationException("token认证失败");
        }
        //查询当前
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //其实这里应该是查询当前用户的角色或者权限去的,意思就是将当前用户设置一个svip和vip角色
        //权限设置一级权限和耳机权限 正常来说应该是去读取数据库只添加当前用户的角色权限的 
        info.addRole("vip");
        info.addRole("svip");
        info.addStringPermission("一级权限");
        info.addStringPermission("二级权限");
        System.out.println("方法结束咯-------》》》");

        return info;
    }

    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证-----------》》》》");
        System.out.println("1.toString ------>>> " + authenticationToken.toString());
        System.out.println("2.getCredentials ------>>> " + authenticationToken.getCredentials().toString());
        System.out.println("3. -------------》》 " +authenticationToken.getPrincipal().toString());
        String jwt = (String) authenticationToken.getCredentials();

//        if (!JwtUtils.verify(jwt)) {
//            throw new AuthenticationException("Token认证失败");
//        }

        return new SimpleAuthenticationInfo(jwt, jwt, "MyRealm");
    }
}

5.写JWTFiler(JWT过滤器)

JWT过滤器有两种写法我只写其中一种,继承BasicHttpAuthenticationFilter
另外一种方法请自行百度
详情解释请看代码

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;


public class JwtFilter extends BasicHttpAuthenticationFilter {

 	
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("JwtFilter -----> preHandle() 方法执行");
        HttpServletRequest req= (HttpServletRequest) request;
        HttpServletResponse res= (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin",req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers",req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);


   
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println("JwtFilter -----> isAccessAllowed() 方法执行");
        
        if (isLoginAttempt(request,response)) {
           executeLogin(request, response);
           return true;
        }
        return true;
    }


    
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println( "JwtFilter -----> isLoginAttempt() 方法执行");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("token");
        return token != null;
    }


    
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        System.out.println("JwtFilter -----> executeLogin() 方法执行");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("token");
        JwtToken jwtToken = new JwtToken(token);
        //然后交给自定义的realm对象去登陆, 如果错误他会抛出异常并且捕获

        System.out.println("-----执行登陆开始-----");
        getSubject(request, response).login(jwtToken);
        System.out.println("-----执行登陆结束----- 未抛出异常");
        return true;
    }
 }
6.配置ShiroConfig将配置注入到容器中
import com.dfinfo.smalabelbackend.filter.JwtFilter;
import com.dfinfo.smalabelbackend.filter.MyRealm;
import org.apache.shiro.mgt.DefaultSessionStorageevaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.linkedHashMap;
import java.util.Map;



@Configuration
public class ShiroConfig {
    
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义 realm.
        securityManager.setRealm(myRealm);
        // 关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageevaluator defaultSessionStorageevaluator = new DefaultSessionStorageevaluator();
        defaultSessionStorageevaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageevaluator(defaultSessionStorageevaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }


    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        //添加自己的过滤器 并且取名为filter
        Map filterMap = new linkedHashMap<>();
        //设置自定义的JWT过滤器
        filterMap.put("jwt",  new JwtFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);

        //设置无权限跳转的url 权限验证如果没权限跳转  
        factoryBean.setUnauthorizedUrl("/noRole");
        Map filterRuleMap = new HashMap<>();

        //设置需要通过过滤器的请求 
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){

        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){

        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
7.配置权限校验或者角色校验

在你的接口上添加注解就好了 上面是角色注解 下面是权限 拥有这些的才能访问接口

前面也说了 myrealm类中 的两个方法 如果不加注解的话 权限方法是根本不会执行的 可以自己做测试

8.来看看结果

1.登陆接口直接放行的
看 并没有输出JWT过滤器中的内容 所有能够用理解anon的作用了吧

2.没放行,也没权限校验的接口
登陆接口那个字符串是上面输出的 不管
这里很容易就能理解了执行的顺序
jwt过滤器里面的先执行 然后按方法执行
因为没有判断权限注解所以只吊了认证方法 认证没错误 然后到接口

3. 有权限角色认证
权限或者角色错误时
看图 :我设置的权限和角色和接口的并不一致 ,结果报错,然后再看,使用了权限注解后授权方法就执行了,这就是整个执行顺序


信息补充的话去这个博客查看
Springboot + Shiro

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/353524.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号