1

shiro入门


两大框架对比:安全框架Shiro和SpringSecurity的比较

了解shiro

什么是Shiro

  1. Apache Shiro是一个Java的安全(权限)框架。|
  2. Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。

shiro的功能

shiro 安全框架整合 JWT 开发_用户名

Authentication:身份认证、登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!

Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;

# ------------------------------------------------------------------------------------

Web Support: Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率

Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了


优点

  • 简单的身份认证, 支持多种数据源
  • 对角色的简单的授权, 支持细粒度的授权(方法级) c、支持一级缓存,以提升应用程序的性能
  • 适用于 Web 以及非 Web 的环境e、非常简单的加密 API
  • 不跟任何的框架或者容器捆绑, 可以独立运行

Shiro的架构


shiro 安全框架整合 JWT 开发_自定义_02shiro 安全框架整合 JWT 开发_自定义_03

         功能

           描述

Subject

用户、主体

Securitymanager

管理所有用户

Authorizer

认证器、是一个接口

Authorizer

授权器、判断操作权限

Realm

连接数据

注意:Shiro 不会去维护用户、_维护权限;这些需要我们自己去设计/提供:然后通过相应的接口沣入给Sniro即可。


依赖与配置


  • 依赖

 <!-- shiro -->

<dependency>

        <groupId>org.apache.shiro</groupId>

        <artifactId>shiro-core</artifactId>

        <version>1.7.1</version>

</dependency>

  • 日志配置(log4j)

#Default Shiro Logging

1og4j.1ogger.org.apache.shiro=INFO


#Disable verbose logging

log4j.logger.org.apache.shiro.util.ThreadContext=WARN

1og4j.1ogger.org.apache.shiro.cache.ehcache.EhCache=WARN

  • shiro.iniini文件说明
  1. 1、[mian] : 定义全局变量
  • 内置securityManger对象
  • 操作内置对象时,在[main]里面写东西

[main] 

securityManger.属性=值

# --

user=com.qd.user

securityManger.对象属性=$user

  1. 2、[users] : 定义用户名与密码

[users]

# 定义用户名为qd666

qd666 = 123456

# 定义用户名为qd666 同时具有admin、role1的角色

qd666 = 123456, admin,role1

  1. 3、[roles] : 定义角色

[roles]

role1=权限名1,权限名2

admin=权限名3,权限名4

# --- 用户表的查询,添加权限

role1=user:query,user:add

  1. 4、[urls] : 定义哪些内置urls生效.在web应用时使用
[urls]
# url地址=内置filter或自定义filter
# 访问时出现/login的url必须要去认支持authc对应的filter
/login=authc
# 任意的url都不需要进行认证等功能
/**=anon
# 所有的内容都必须保证用户已经登录
/**=user
# url abc访问时必须保证用户具有role1和role2的角色
/abc=roles["role1,role2"]

shiro 安全框架整合 JWT 开发_自定义_04

Realm结构


shiro 安全框架整合 JWT 开发_用户名_05



2

功能实现


配置实现认证


  1. 1、身份验证在shiro 中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:
  2. 2、principals身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
  3. 3、credentials证明/凭正,即只有主体知道的安全值,如密码/数字证书等最常见的principals和credentials组合就是用户名/密码了。

认证流程shiro 安全框架整合 JWT 开发_用户名_06

@Slf4j

public class Certification {

    //创建安全管理器工厂

    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

    //使用工厂创建安全管理器

    SecurityManager securityManager = factory.getInstance();

    //把当前的安全管理器绑定到当前的线程

    SecurityUtils.setSecurityManager(securityManager);

    //获取当前的用户对象

    Subject currentUser = SecurityUtils.getSubject();


    //通过当前对象拿到Session

   // Session session = currentUser.getSession();

   // session.setAttribute("someKey","aValue");

   // String value = (String) session.getAttribute("someKey");


   // 判断用户是否认证通过

    if(!currentUser.isAuthenticated()) {

       //封装用户名与密码

        AuthenticationToken token = new UsernamePasswordToken("lonestarr""vespa");

        //设置记住我

        token.setRememberMe(true);

        try {

            //进行认证

            currentUser.login(token);  //执行了登录

        } catch (LockedAccountException lae) {   //用户锁定

            log.error(token.getPrincipal() + " is locked");

        } catch (AuthenticationException e) {

            log.error("用户名或密码不正确");

        }

    }

}


配置实现授权



授权,也叫访问控制,即在应用中控利谁能访问哪些资源―如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)角色(Role)。另外关注公号“终码一生”,回复关键词“资料”,获取视频教程和最新的面试资料!

         对象

        描述

主体

访问应用的用户

资源

某些页面、数据

权限

判断用户有无操作的权力

角色

权限的集合


授权流程shiro 安全框架整合 JWT 开发_用户名_07

//接认证       

    //打印其识别主体

        log.info("User ["+currentUser.getPrincipal()+"] logged in successfully.");


        //角色判断 统一返回布尔值

        currentUser.hasRole("role1");

        List<String> list= Arrays.asList("role1","role2");

        //判断当前用户是否拥有list集合中的所有角色

        currentUser.hasAllRoles(list);

//--------------------------------------------------------

        //权限判断

        currentUser.isPermitted("user:query");

        //判断是否拥有全部的权限

        currentUser.isPermitted("user:query","user:add");

//--------------------------------------------------------

        //注销

        currentUser.logout();

        //结束

        System.exit(0);

认证与授权中主要的方法

 //获取当前的用户对象

        Subject currentUser = SecurityUtils.getSubject();

 //通过当前对象拿到Session

        Session session = currentUser.getSession();

//判断用户是否被认证

  currentUser.isAuthenticated()

//得到认证

        currentUser.getPrincipal()

//判断是否有此角色

        currentUser.hasRole("schwartz")

//判断是否有此权限

        currentUser.isPermitted("lightsaber:wield")

//注销

        currentUser.logout();

注意: Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,但是开发中我们需要从数据库中获取数据,所以我们需要自定义Realm!


自定义Realm实现认证


需继承的接口

接口

描述

CachingRealm

负责缓存处理

AuthentictionRealm

负责认证

AuthorizingRealm

负责授权

通常自定义的realm继承AuthonizingRealm

  • shiro配置类 固定步骤

@Configuration

public class ShiroConfig {

    //1.Realm 资源  自定义userRealm对象

    @Bean

    public Realm userRealm() { return new UserRealm(); }

    //2.securityManager 执行流程控制

    @Bean("securityManager")

    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联Realm对象

        securityManager.setRealm(userRealm);

        return securityManager;

    }

    //3.ShiroFilterFactoryBean 请求过滤器

    @Bean

    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {

        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器

        bean.setSecurityManager(defaultWebSecurityManager);

      //添加过滤器 设置默认请求等操作

        return bean;

    }

}

在ShiroConfig 添加过滤器

map中的key值是ant路径

**   代表多级路径

*    代表单极路径

?    代表一个字符

# -----value是默认过滤器-------------

anon  : 无需认证就可以访问

authc : 必须认证了才能访问

user  : 必须拥有 记住我 功能才能用

perms[] : 拥有对某个资源的权限才能访问

role  : 拥有某个角色权限才能访问

  • *Realm类 继承AuthorizingRealm

public class UserRealm extends AuthorizingRealm {

    //授权方法

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("执行了授权方法");

     //....

        return null;

    }


    //认证方法

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("执行了认证方法");

      //......

        return null;

    }

}


自定义Realm实现授权



  1. 1、对某些页面上的属性/组件进行授权

可以使用session解决  

在登录时将currentUser.getPricipal()放入session中

将currentUser.getPricipal()来自于MyRealm中doGetAuthenticationinfo认证方法返回的SimpleAuthenticationInfo对象的第一个属性。前台获取session判断即可

  1. 2、控制后台资源路径的访问权限
  • 先访问需要授权的页面

//硬编码方式,代码冗余  

  @RequestMapping("/xxx")

    @ResponseBody

    public String unauthorized() {

        Subject subject = SecurityUtils.getSubject();

        if (subject.isPermitted("vip1")) {

            return "ok";

        } else {

            return "未经授权无法访问此页面";

        }

    }


//-----方法2

//设置未授权的请求(页面)  这种方式需要添加大量的过滤器配置

bean.setUnauthorizedUrl("/xxx");


//-----方法3

//注解  如下

//硬编码方式,代码冗余  

    @RequiresPermissions("vip1")

  @RequestMapping("/xxx")

    @ResponseBody

    public String unauthorized() {

       return "ok";

    }


                注解

                       描述

@RequiresAuthentication

需要完成用户登录

@RequiresGuest

未登录用户可以访问,登录用就不能访问。

@RequiresPermissions

需要有对应资源权限

@RequiresRoles

需要有对应的角色

@RequiresUser

需要完成用户登录并且完成了记住我功能。

tips : 当没有权限访问时,就会抛出500异常 AuthorizationException 统一处理


模拟登录


登录请求 建议定义全局异常处理

 @RequestMapping("/login")

    public String login(String username, String password) {

        //获取当前的认证

        Subject subject = SecurityUtils.getSubject();

        //封装用户的登录数据

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        //执行登录方法 固定代码  若无异常就成功

        try {

            subject.login(token);

            return "index";

        } catch (AuthenticationException e) { //用户名不存在

            model.addAttribute("msg""用户名或者密码错误");

            return "login";

        } 

userRealm

//@Configuration

public class UserRealm extends AuthorizingRealm {


     //授权方法

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("执行了授权方法");

        //获取当前用户

        Subject subject = SecurityUtils.getSubject();

        User currentUser = (User) subject.getPrincipal();

        //定义info,封装这个对象所有的角色和权限

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //判断登录者是否为超级管理员 权限为所有

        if ("超级管理员标志".equals(currentUser.getPermId())){  

            //1.获取角色与权限列表

            //2.绑定需要角色与权限

            info.addRole("");

            info.addStringPermission("");

        }else {

            //管理员

            info.addRole("admin");

            info.addStringPermission("*.*");

        }

        return info;

    }


    //认证方法

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("执行了认证方法");

        //获取当前用户

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //查询真实数据库

       // User user = userService.queryUserByName(userToken.getUsername());

        //若查询结果为空,会抛出异常

        if (user == null) {

            return null;

        }

        //密码认证,shiro帮我们自动匹配 ,之后就完成认证

        return new SimpleAuthenticationInfo(user, user.getUserPwd(), "");  //资源,密码

    }

}

        //设置安全管理器

        bean.setSecurityManager(defaultWebSecurityManager);

        Map<String, String> filterMap = new LinkedHashMap<>();

        //添加过滤器

        filterMap.put("/user/addUser""perms[user:add]");

        filterMap.put("/user/*""authc");

        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求(页面) 否则会报错

        bean.setLoginUrl("/login");

        //设置未授权的请求(页面)

        bean.setUnauthorizedUrl("/xxx");

        return bean;


tips: 越详细的规则放入前面哦!

退出登录

 //----方法1----请求

  Subject subject = SecurityUtils.getSubject();

  subject.logout();


//----方法2----过滤器

 filterMap.put("/user/logout""logout");



3

其他功能


密码加密


shiro会获得一个credentialsMatcher对象,来对密码进行比对。想要用MD5方式进行加密:Md5CredentialsMatcher已经过期,要使用``HashedCredentialsMatcher`并设定算法名。

  1. 1、在``securityManager`设置CredentialsMatcher

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();

//设置加密算法

matcher.setHashAlgorithmName("MD5");

//设置加密次数

matcher.setHashIterations(2);

userRealm.setCredentialsMatcher(matcher);

  1. 2、加盐 改变构造方法

 return new SimpleAuthenticationInfo(user, ByteSource.Util.bytes(user.getUserPwd()), ByteSource.Util.bytes("12345"),"");  

  1. 3、匹配

public class PasswordEncoder {

    public static String encoder(String password) {

        SimpleHash simpleHash = new SimpleHash("MD5", ByteSource.Util.bytes(password), ByteSource.Util.bytes("12345"), 2);

        return simpleHash.toString();

    }

}


多Realm认证



举个栗子:在登录微信时,我们可以选择使用手机号、也可以选择微信号进行登录,那么这种时怎么实现的呢?

  • 在user表与实体中加入手机号、账号字段
  • 新建MobileRealm

@Configuration

public class MobileRealm extends AuthenticatingRealm {


    @Autowired

    UserService userService;

    //认证

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("执行了认证方法");

        //获取当前用户

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //查询真实数据库

        User user = userService.queryUserByName(userToken.getUsername());

        //若查询结果为空,会抛出异常

        if (user == null) {

            return null;

        }

        //密码认证,shiro帮我们自动匹配 ,之后就完成认证

        return new SimpleAuthenticationInfo(user, user.getUserPwd(),"");  

    }

}

  • 设置securityManager

//关联对象

securityManager.setRealms(Arrays.asList(userRealm,mobileRealm));

此时已经可以实现通过手机号也能登录账号 在多Realml的认证策略中,AuthenticationStrategy接口有三个实现类,了解

shiro 安全框架整合 JWT 开发_自定义_08


Remember me


  • 一般前端会在页面提供记住我选项
  • 实现

 if (subject.isAuthenticated() && subject.isRemembered()) {

      subject.login(token);

      token.setRememberMe(true);

 }   

  • 记住我功能默认对应了``user` 过滤器
  • 设置securityManager

CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();

SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

//防止xss读取cookie

simpleCookie.setHttpOnly(true);

//记住我cookie生效时间30天 ,单位秒

simpleCookie.setMaxAge(2592000);

rememberMeManager.setCookie(simpleCookie);

 securityManager.setRememberMeManager(rememberMeManager);

在微服务与分布式中 涉及到cookie共享问题 等等等头大...


认证缓存


我们每次登录时可以先从缓存中获取,如果没有在从数据库中查询,提高性能

  • 在securityManager开启缓存

//缓存

AbstractCacheManager cacheManager = new MemoryConstrainedCacheManager();

securityManager.setCacheManager(cacheManager);

记住我,会话管理以及认证缓存,都可以通过扩展对应的manager接口的方式,实现自己的灵活扩展,比如将信息共享到redis。另外关注公号“终码一生”,回复关键词“资料”,获取视频教程和最新的面试资料!


结合Thylemeaf


  1. 1、导入依赖

<!--导入shiro与Thymeleaf整合包-->

<dependency>

        <groupId>com.github.theborakompanioni</groupId>

        <artifactId>thymeleaf-extras-shiro</artifactId>

        <version>2.0.0</version>

</dependency>

  1. 2、配置ShiroConfig

    //整合ShiroDialect:用来整合shiro与thymeleaf

    @Bean

    public ShiroDialect getShiroDialect() {

        return new ShiroDialect();

    }

  1. 3、具体使用 :shiro springsecurity thymeleaf的标签使用和命名空间
  2. 4、注解讲解:注解使用



4

整合JWT


至此,终于到了真正想写的地方,在实际的开发中,接口设计通常要考虑到安全与权限设计,JWT的token机制也不会占用服务器内存、利用JWT也能实现单点登录等等功能。下面简单整合两者的使用吧JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。我们利用一定的编码生成 Token,并在 Token 中加入一些非敏感信息,将其传递。当用户登陆成功将颁发给用户一个token,token由jwt制作。往后token过期之前,用户访问的凭借都是token。如果token过期或者被篡改,则要求用户重新登陆。在token有效时,用户访问需要权限的接口都要经过shiro的两个核心注解RequiresRoles以及RequiresPermissions的验证。

  1. 1、导入依赖

<!--配置shiro -->

<dependency>

        <groupId>org.apache.shiro</groupId>

        <artifactId>shiro-spring</artifactId>

        <version>1.7.1</version>

</dependency>

<!--配置JWT-->

<dependency>

        <groupId>com.auth0</groupId>

        <artifactId>java-jwt</artifactId>

        <version>3.4.0</version>

</dependency>

  1. 2、JWTUtil
    生成token与校验token 指定 token 过期时间 EXPIRE_TIME 和签名密钥 SECRET

public class JwtUtil {

    //密钥

    private static final String SECRET = "!Q464rwr(&*)6%$#^%&JNJ46da";


    /**

     * 生成token

     * @param map:要加密的信息

     * @return 令牌

     */

    public static String sign(Map<String, String> map) {

        //设置过期时间 默认3天

        Calendar instance = Calendar.getInstance();

        instance.add(Calendar.DATE, 3);

        JWTCreator.Builder builder = JWT.create();

        //payload

        map.forEach((k, v) -> {

            builder.withClaim(k, v);

        });

        //指定令牌过期时间与加密

        String token = builder.withExpiresAt(instance.getTime())

                .sign(Algorithm.HMAC256(SECRET));

        return token;

    }


    /**

     * 验证token合法性/获取token全部信息

     * @param token

     * @return verify

     */

    public static DecodedJWT verify(String token) {

        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);

    }


    /**

     * 获取token单个信息

     * @param token

     * @param s:key

     * @return value

     */

    public static String getInfo(String token, String s) {

        DecodedJWT verify = verify(token);

        return verify.getClaim(s).asString();

    }

}

  1. 3、JWTFilter
    在上面,我们使用的是 shiro 默认的权限拦截 Filter,而因为 JWT 的整合,我们需要自定义自己的过滤器 JWTFilter

public class JWTFilter extends BasicHttpAuthenticationFilter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());


    /**

     * 如果带有 token,则对 token 进行检查,否则直接通过

     */

    @Override

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {

        //判断请求的请求头是否带上 "Token"

        if (isLoginAttempt(request, response)) {

            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确

            try {

                executeLogin(request, response);

                return true;

            } catch (Exception e) {

                System.out.println("token 错误");

                //token 错误   非法访问

                throw new RuntimeException("Unauthorized access!");

               // responseError(response, e.getMessage());

            }

        }

        //如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true

        return true;

    }


    /**

     * 判断用户是否想要登入。

     * 检测 header 里面是否包含 Token 字段

     */

    @Override

    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {

        HttpServletRequest req = (HttpServletRequest) request;

        String token = req.getHeader("Authorization");

        return token != null;

    }


    /**

     * 执行登陆操作

     */

    @Override

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        String token = httpServletRequest.getHeader("Authorization");

        JWTToken jwtToken = new JWTToken(token);

        // 提交给realm进行登入,如果错误他会抛出异常并被捕获

        getSubject(request, response).login(jwtToken);

        // 如果没有抛出异常则代表登入成功,返回true

        return true;

    }


    /**

     * 对跨域提供支持

     */

    @Override

    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));

        httpServletResponse.setHeader("Access-Control-Allow-Methods""GET,POST,OPTIONS,PUT,DELETE");

        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态

        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

            httpServletResponse.setStatus(HttpStatus.OK.value());

            return false;

        }

        return super.preHandle(request, response);

    }


    /**

     * 将非法请求跳转到 /unauthorized/**

     */

    private void responseError(ServletResponse response, String message) {

        try {

            HttpServletResponse httpServletResponse = (HttpServletResponse) response;

            //设置编码,否则中文字符在重定向时会变为空字符串

            message = URLEncoder.encode(message, "UTF-8");

            httpServletResponse.sendRedirect("/unauthorized/" + message);

        } catch (IOException e) {

            logger.error(e.getMessage());

        }

    }

}


  1. 4、JWTToken
    实现 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;

    }

}

  1. 5、ShiroConfig
    设置好自定义的Filter,让请求通过过滤器

@Configuration

public class ShiroConfig {

    /**

     * 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证

     */

    @Bean

    public ShiroFilterFactoryBean factory(SecurityManager securityManager) {

        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt

        Map<String, Filter> filterMap = new LinkedHashMap<>();

        //设置我们自定义的JWT过滤器

        filterMap.put("jwt", new JWTFilter());

        factoryBean.setFilters(filterMap);

        factoryBean.setSecurityManager(securityManager);

        // 设置无权限时跳转的 url;

        factoryBean.setUnauthorizedUrl("/unauthorized/无权限");

        Map<String, String> filterRuleMap = new HashMap<>();

        // 所有请求通过我们自己的JWT Filter

        filterRuleMap.put("/**""jwt");

        // 访问 /unauthorized/** 不通过JWTFilter

        filterRuleMap.put("/unauthorized/**""anon");

        factoryBean.setFilterChainDefinitionMap(filterRuleMap);

        return factoryBean;

    }


    /**

     * 注入 securityManager

     */

    @Bean

    public SecurityManager 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 DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

        // 强制使用cglib,防止重复代理和可能引起代理出错的问题

        // https://zhuanlan.zhihu.com/p/29161098

        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);

        return defaultAdvisorAutoProxyCreator;

    }


    @Bean

    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {

        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();

        advisor.setSecurityManager(securityManager);

        return advisor;

    }


    @Bean

    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {

        return new LifecycleBeanPostProcessor();

    }

}

  1. 6、Realm类
    自定义我们的Realm 实现认证与授权

@Component

public class MyRealm extends AuthorizingRealm {

    private final UserMapper userMapper;


    @Autowired

    public CustomRealm(UserMapper userMapper) {

        this.userMapper = userMapper;

    }


    /**

     * 必须重写此方法,不然会报错

     */

    @Override

    public boolean supports(AuthenticationToken token) {

        return token instanceof JWTToken;

    }


    /**

     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。

     */

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        System.out.println("————身份认证方法————");

        String token = (String) authenticationToken.getCredentials();

        // 解密获得username,用于和数据库进行对比

        String username = JWTUtil.getInfo(token,"username");

        if (username == null) {

            throw new AuthenticationException("token认证失败!");

        }

        User user = userMapper.queryUserByName(username);

        if (user.getPassWord() == null) {

            throw new AuthenticationException("该用户不存在!");

        }

        return new SimpleAuthenticationInfo(token, token, "MyRealm");

    }


    /**

     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的

     */

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        System.out.println("————权限认证————");

        String token= principals.toString();

        String username = JWTUtil.getInfo(token,"username");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //获得该用户角色

        User user = userMapper.queryUserByName(username);

        //每个角色拥有默认的权限

        int perm = user.getPerm();

        //每个用户可以设置新的权限

        //  String permission = userMapper.getPermission(username);

        Set<String> roleSet = new HashSet<>();

        Set<String> permissionSet = new HashSet<>();

        //需要将 role, permission 封装到 Set 作为 info.setRoles(), info.setStringPermissions() 的参数

        roleSet.add(perm + "");

        //   permissionSet.add(rolePermission);

        //  permissionSet.add(permission);

        //设置该用户拥有的角色和权限

        info.setRoles(roleSet);

        info.setStringPermissions(permissionSet);

        return info;

    }

}

  1. 7、Controller

    @PostMapping("/login")

    public ResultMap login(@RequestParam("username") String account,

                           @RequestParam("password") String password) {

        User user = userMapper.queryUserByName(account);

        if (user == null || !user.getPassWord().equals(password)) {

            throw new RuntimeException("用户名或密码错误");

        }

        Map<String, String> map = new HashMap<>();

        map.put("username", user.getAccount());

        return R.ok(JwtUtil.sign(map), "登录成功");

    }



    /**

     * 拥有 1、2、3 角色的用户可以访问下面的页面

     */

    @GetMapping("/getMessage")

    @RequiresRoles(logical = Logical.OR, value = {"1""2""3"})

    public R getMessage() {

       return R.ok("""成功获得信息");

    }

运行结果:shiro 安全框架整合 JWT 开发_自定义_09

不带token访问的情况下:shiro 安全框架整合 JWT 开发_用户名_10

加上的情况:shiro 安全框架整合 JWT 开发_ide_11

  PS:防止找不到本篇文章,可以收藏点赞,方便翻阅查找哦。