Shiro学习与笔记

Shiro

Shiro理论

什么是shiro

Shiro是一个强大易用的java安全,提供了认证、授权、加密、会话管理、与web集成、缓存等功能,对于任何一个应用程序,都可以提供全面的安全服务,相比其他安全框架,shiro要简单的多。

功能

  • 身份验证(核心功能);
  • 资源授权(核心功能);
  • 密码加密(非核心功能);
  • 会话管理(非核心功能);
  • Remember Me(非核心功能);

组件

  • Subject:应用层和shiro框架交互的对象
  • Realm:实现身份验证和资源授权的核心组件:域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
  • RememberMeManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于spring mvc中的dispatcherServlet前端控制器。
    • SimpleCookie:记住我——Cookie
    • CookieRememberMeManager:记住我—— Cookie 管理器;
  • SessionManager
    • SimpleCookie:会话管理—— Cookie
    • DefaultWebSessionManager:会话配置;
  • SecurityManager:安全管理器,它负责与 Shiro 的其他组件进行交互,需要注入 RealmRememberMeManagerSessionManager 等组件,且管理着所有的 Subject;
  • ShiroFilterFactoryBeanShiro 过滤器工厂,注入 SecurityManager、定义过滤器、指定路径拦截规则等;
  • ShiroDialect:Shiro 方言,支持 Thymeleaf 页面 Shiro 标签;
  • DefaultAdvisorAutoProxyCreator:Advisor 代理类生成器;
  • AuthorizationAttributeSourceAdvisor:创建 Advisor 代理类,扫描 Shiro 注解;
  • 应用代码通过 Subject 来进行认证和授权,Subject 委托给 SecurityManager,我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能对用户及其权限进行判断;

img

shiro注解

  • @RequiresAuthentication : 表示当前 Subject 需要登录;
  • @RequiresUser : 表示当前 Subject 需要登录或记住我;
  • @RequiresGuest : 表示当前 Subject 是游客身份;
  • @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):当前 Subject 需要的角色;
  • @RequiresPermissions (value={““,””}, logical= Logical.OR) :当前 Subject 需要拥有的资源;

既可以用在controller中,也可以用在service中建议将shiro注解放入controller,因为如果service层使用了spring的事物注解,那么shiro注解将无效

shiro的优点

1、 简单的身份验证,支持多种数据源
2、对角色的简单授权,支持细粒度的授权(方法)
3、支持一级缓存,以提升应用程序的性能
4、内置基于POJO的企业会话管理,适用于web及非web环境
5、非常简单的API加密
6、不跟任何框架绑定,可以独立运行

运行流程

  1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
  2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
  3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
  5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 则抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

前端标签

jsp页面
首先要导入标签库;
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>:导入标签库;
    
<shiro:guest></shiro:guest>:游客访问;
    
<shiro:user></shiro:user>:用户需要登录或记住我;
    
<shiro:authenticated></shiro:authenticated>:用户需要登录;
    
<shiro:hasRole name="admin"></shiro:hasRole>:用户需要拥有某种角色访问;
    
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>:用户需要拥有某些角色;
    
<shiro:hasPermission name="user:create"></shiro:hasPermission>:用户需要拥有某资源;进行显示
    

Thymeleaf
导入
<html 
       xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
      >
    
</html>

shiro:guest:游客访问;
shiro:user:用户需要登录或记住我;
shiro:authenticated:用户需要登录;
shiro:hasRole:用户需要某角色;
shiro:hasAnyRoles:用户需要某些角色;
shiro:hasPermission:用户需要某权限;
-----------------------------------------------------------
<li shiro:hasAnyRoles="admin,manager">:用户需要 admin 或 manager 角色可访问;
    
<li><span shiro:principal/></li>:身份验证器包装的用户名时,页面获取用户名;

<span shiro:principal property="loginName"/>:身份验证器中包装 user 对象时,页面获取用户名;

<input type="hidden" id="userId" th:value="${session.user.userId}">:页面获取 session 中 user 对象;

在spring框架中集成shiro

Pom文件进行依赖配置

可以在maven仓库包https://mvnrepository.com/,根据三要素进行查找

<!-- shiro -->
<properties>
    <shiro.version>1.7.1</shiro.version>
</properties>
<!-- shiro配置 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>${shiro.version}</version>
    </dependency>

<!-- 支持Spring框架集成-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>${shiro.version}</version>
    </dependency>

<!-- 支持基于web的应用程序 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-web</artifactId>
   <version>${shiro.version}</version>
</dependency>

<!-- 启用对Shiro AOP和注释的AspectJ支持。 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-aspectj</artifactId>
   <version>${shiro.version}</version>
</dependency>

<!-- 启用基于ehcache的框架缓存。 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-ehcache</artifactId>
   <version>${shiro.version}</version>
</dependency>

项目跑起来了,只是控制台输出了

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

需要配置日志包

shiro使用slf4j作为日志框架,所以必需配置slf4j。同时,使用log4j作为底层的日志实现框架。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.25</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.25</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>

集成Shiro

在Spring框架中集成Shiro,本质上是与Spring IoC容器和Spring MVC框架集成.

web.xml中的设置
  <!-- shiro过滤器 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 设置true由servlet容器控制filter的生命周期 -->
    <!--官方解释到,支持targetBeanName 通过 init-param 这个节点来设置-->
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
shiro的bean配置文件,

其中包含过滤设置。此文件名为spring-shiro.xml,里面定义了需要的Bean,完成诸多功能。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
	   http://www.springframework.org/schema/context/spring-context-4.3.xsd

">   
    <!-- 这些类注册为spring容器中的bean-->
    <context:component-scan base-package="com.qq"/>
    
     <!--注入自定义的Realm-->
    <bean id="userRealm" class="com.qq.realm.UserRealm"/>
    
    <!--开启shiro的注解-->
    <bean id="advisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
        <!--必须 否则shiro就无法解析权限url-->
        <property name="proxyTargetClass" value="true"/>
    </bean>
    
    <!-- Shiro 生命周期处理器 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 -->
    
    <!--让RequiredPermissions生效-->
     <!-- 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/> <!-- 这句可能要打开才能启用注解 -->
    </bean>
    
    <!-- Security Manager -->
       <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--        定义了认证逻辑和授权逻辑-->
        <property name="realm" ref="userRealm"/>
    </bean>
    
    
     <!-- Shiro的Web过滤器 id 必须 和web.xml文件中配置的一致-->
    <!--配置ShiroFilter-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <!-- Shiro的安全管理器,所有关于安全的操作都会经过SecurityManager -->
      <property name="securityManager" ref="securityManager"/>

      <!-- 认证页面:未认证用户请求需要认证的url时就会自动跳转到此页面url。-->
        <!-- 它不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的"/login.jsp"。-->
        <property name="loginUrl" value="/login/view"/>

        <!-- 没有授权的用户请求需要授权的url时就会被跳转到这个地址 -->  <!-- 但是我发现这句不起作用 -->
        <property name="unauthorizedUrl" value="/login/view" />

        <!-- 认证成功页面url:-->
        <!-- 理想状况下,它是认证成功后默认跳转路径,不设置则跳转至"/"。-->
        <!-- 理想状况下,如果点击一个需认证的url,经认证后则会自动跳转到之前这个需要认证的url,而不跳到successUrl。-->
        <!-- 但上面所说的仅是理想状况下 successUrl的功效。实验时往往差强人意-->
        <property name="successUrl" value="/home"/>

        <!--
        <property name="filters">
            <map>
                <entry key="logout" value-ref="logoutFilter" />
            </map>
        </property>
         -->

        <!-- URL的过滤规则指定-->
        <!-- 用得最多的是  
           anon(匿名可访问),
           authc(认证后可访问),
           logout(注销时用)
        (会清除所有sesseion和一些cookie)(会默认跳往根目录) 
         user:登录过能访问
         roles:角色过滤器
         -->
        <property name="filterChainDefinitions" >
            <value>
                /login/view = anon
                /back/student/zzz =authc,roles[admin]
                /back/** = authc
            </value>
        </property>


   
</beans>


或者:springShiro.xml


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context" 

    xsi:schemaLocation="http://www.springframework.org/schema/beans 

            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 

            http://www.springframework.org/schema/context  

            http://www.springframework.org/schema/context/spring-context-4.0.xsd"

    default-lazy-init="true">

    

    <!-- Realm 配置 -->

    <bean id="myRealm" class="com.sfac.springMvc.config.MyRealm" />

    

    <!-- Remember Me 配置 -->

    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">

        <property name="name" value="rememberMe"/>

        <property name="maxAge" value="604800"/>

        <property name="httpOnly" value="true"/>

    </bean>

    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">

        <property name="cookie" ref="rememberMeCookie"/>

    </bean>

    

    <!-- Session 配置 -->

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">

        <!-- Session 的失效时长,单位毫秒 -->

        <property name="globalSessionTimeout" value="6000000"/>

        <!-- 删除失效的 Session -->

        <property name="deleteInvalidSessions" value="true"/>

        <!-- 去掉重定向后 Url 追加 Session Id -->

        <property name="sessionIdUrlRewritingEnabled" value="false" />

    </bean>

    

    <!-- Security Manager -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

        <property name="realm" ref="myRealm"/>

        <property name="rememberMeManager" ref="rememberMeManager"></property>

        <property name="sessionManager" ref="sessionManager" />

    </bean>

    

    <!-- 

        Shiro Filter 

        anon:匿名访问,无需登录

        authc:登录后才能访问

        user:登录过能访问

        logout:登出

        roles:角色过滤器

    -->

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <property name="securityManager" ref="securityManager"/>

        <property name="loginUrl" value="/login"/>

        <property name="successUrl" value="/common/dashboard"/>

        

        <property name="filterChainDefinitions">  

            <value>  

                /static/**=anon

                /register=anon

                /login=anon

                /forgot=anon

                /logout=logout

                /api/**=anon

                /**=authc

            </value>  

        </property>  

    </bean>

    

    <!-- 配置验证表单名称,默认是:userName,password -->

    <!-- <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="usernameParam" value="account"></property>
        <property name="passwordParam" value="password"></property>

    </bean> -->

    

    <!-- Shiro 生命周期处理器 -->

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 -->

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

        depends-on="lifecycleBeanPostProcessor">

        <property name="proxyTargetClass" value="true" />

    </bean>

    <!-- 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解 -->

    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

        <property name="securityManager" ref="securityManager" />

    </bean>

</beans>

必须是在MVC配置文件中用

<import resource=*"spring-shiro.xml"*></import>

将spring-shiro.xml载入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBZwMJxH-1662297213062)(C:\Users\飞\AppData\Roaming\Typora\typora-user-images\image-20220902220333286.png)]

例子:

  • 身份验证

    UserRealm

    package com.qq.realm;
    import com.qq.model.User;
    import com.qq.service.UserService;
    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;
    
    /**
     * @description:com.qq.realm_studyssm
     * @author: 霏宇
     * @time: 2022/8/5,9:55
     */
    public class UserRealm extends AuthorizingRealm {
        @Autowired
        UserService userService;
        //授权:
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            String name =(String)principalCollection.getPrimaryPrincipal();
            if (name.equals("admin")){
                info.addStringPermission("admin");
            }else if (name.equals("lisi")){
                info.addStringPermission("worker");
                info.addRole("admin");
            }
            return info;
    
        }
        /**
         * 认证
         * @param token
         * @return
         * @throws AuthenticationException
         */
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
          //一旦此用户不存在就主动抛出异常
            UsernamePasswordToken upToken = (UsernamePasswordToken)token;
             String name =upToken.getUsername();
             String password=new String(upToken.getPassword());
    
             User user =userService.selectByName(name);
             if(null==user){
                 throw new UnknownAccountException("你输入的账户不存在!");
             }else {
                 User user2 =userService.selectByNamePassword(name,password);
                 if(null==user2){
                     throw new IncorrectCredentialsException("密码错误");
                 }else {
                     return  new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName());
                 }
             }
    
    //         if(!name.equals("list")&&!name.equals("admin")){
    //             throw new UnknownAccountException("你输入的账户不存在!");
    //         }else {
    //             //这里假设系统的用户只有两个 (zhangsan,123456)、(lisi,888999)
    //             if(name.equals("list")&&password.equals("789") ||name.equals("admin")&&password.equals("123456")){
    //
    //                 return   new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName());
    //
    //             }else {
    //                 throw new IncorrectCredentialsException("用户不存在");
    //             }
    //
    //         }
    
    
    
    
        }
    }
    
    

    或者:MyRealm.java

    public class MyRealm extends AuthorizingRealm {
    
        @Autowired
    
        private UserService userService;
    
        @Autowired
    
        private RoleService roleService;
    
        @Autowired
    
        private ResourceService resourceService;
    
    
        /**
    
         * -资源授权器
    
         */
    
        @Override
    
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
            // 授权类
    
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    
            
    
            User user = (User) principals.getPrimaryPrincipal();
    
            if (user == null) {
    
                return null;
    
            }
    
            
    
            List<Role> roles = Optional
    
                    .ofNullable(roleService.getRolesByUserId(user.getId()))
    
                    .orElse(Collections.emptyList());
    
            roles.stream().forEach(role -> {
    
                authorizationInfo.addRole(role.getRoleName());
    
                List<Resource> resources = Optional
    
                        .ofNullable(resourceService.getResourcesByRoleId(role.getId()))
    
                        .orElse(Collections.emptyList());
    
                resources.stream().forEach(resource -> {
    
                    authorizationInfo.addStringPermission(resource.getPermission());
    
                });
    
            });
    
    
            return authorizationInfo;
    
        }
    
        /**
    
         * -身份验证器
    
         */
    
        @Override
    
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            String userName = (String) token.getPrincipal();
    
            User user = userService.getUserByUserName(userName);
    
            if (user == null) {
    
                throw new UnknownAccountException("The account do not exist.");
    
            }
    
            
    
            // realmName: 当前 realm 对象的唯一名字. 调用父类的 getName() 方法
    
            return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    
        }
    
    }
    

    LoginController

    package com.qq.controller;
    
    import com.qq.model.User;
    import com.qq.service.UserService;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpSession;
    
    /**
     * @description:com.qq.controller_studyssm
     * @author: 霏宇
     * @time: 2022/8/4,14:02
     */
    @Controller
    @RequestMapping("/login")
    public class LoginController {
        @Autowired
        UserService userService;
        @RequestMapping("/view")
        String index(){
            return "login/view";
        }
    
        @RequestMapping("/enter")
        String enter(String name, String password, HttpSession session, Model model){
    
    
            Subject subject = SecurityUtils.getSubject();     //
            UsernamePasswordToken token = new UsernamePasswordToken(name, password); //对(用户名、密码)登录方式,用UsernamePasswordToken封装即可
            try {
                subject.login(token);	//调用login()方法。 有可能遭遇两种异常,代表登陆失败。
                User user =userService.selectByNamePassword(name,password);
                session.setAttribute("USER",user);
    
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                model.addAttribute("message", "用户名错误!");
                return "redirect:view";
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                model.addAttribute("message", "密码错误");
    
    
                return "redirect:view";
            }
    
            System.out.println(subject.isAuthenticated()); //打印下看看是否已认证成功
    
            return "redirect:/back/hui/index";
    
    
    //        User user = userService.selectByNamePassword(name, password);
    //        if(user==null){
    //
    //            return "redirect:index";
    //        }else {
    //            session.setAttribute("USER", user);
    //            return "redirect:/back/book/index";
    //        }
    
        }
        @RequestMapping("/logout")
        String logout(HttpSession session){
            session.removeAttribute("USER");
             Subject subject =SecurityUtils.getSubject();
             subject.logout();
           // session.invalidate();
            return "redirect:view";
        }
    
    }
    
    

    或者在

    UserService

    public Result<User> login(User user);
    

UserServiceImpl

    ```java
    @Service
    public class UserServiceImpl implements UserService{
        
    @Override
    
    public ResultEntity<User> login(User user) {
    
        Subject subject = SecurityUtils.getSubject();
    
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), 
    
                MD5Util.getMD5(user.getPassword()));
    
        token.setRememberMe(user.getRememberMe());
        return new Result<User>(Result.ResultStatus.SUCCESS.code, "Success", user);
    
        try {
    
            subject.login(token);
    
            subject.checkRoles();
    
        } catch (Exception e) {
    
            e.printStackTrace();
    
              return new Result<User>(Result.ResultStatus.FAILD.code, e.getMessage());
    
        }
        
        Session session = subject.getSession();
    
        session.setAttribute("user", subject.getPrincipal());
    
        
    
    }
    
    
    @GetMapping("/logout")
    
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
       
        return "redirect:/login";
    
    }
    
    }
    ```

Spring Boot Shiro

POM文件

<!-- shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- thymeleaf 对 shiro 的扩展包,支持页面书写 shiro 标签 -->
        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--thymeleaf shiro 根据权限标签显示-->
        <!--shiro与themeleaf集成-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

在application.properties设置shiro配置

#for shiro
#开启Shiro配置,默认为true
shiro.web.enabled=true

#server.servlet.context-path=/shiro
#spring.application.name=shiro

MyRealm.java

package com.feiyu.sprtingboot.config.shiro;

import com.feiyu.sprtingboot.modules.account.entity.Resource;
import com.feiyu.sprtingboot.modules.account.entity.Role;
import com.feiyu.sprtingboot.modules.account.entity.User;
import com.feiyu.sprtingboot.modules.account.service.ResourceService;
import com.feiyu.sprtingboot.modules.account.service.RoleService;
import com.feiyu.sprtingboot.modules.account.service.UserService;
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 java.util.List;


/**
 * @description:com.feiyu.sprtingboot.config.shiro_sprtingboot
 * @author: 霏宇
 * @time: 2022/8/31,9:41
 */
@Component
public class MyRealm  extends AuthorizingRealm {
   @Autowired
   private UserService userService;
   @Autowired
   private RoleService roleService;
   @Autowired
   private ResourceService resourceService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        SimpleAuthorizationInfo authorization = new SimpleAuthorizationInfo();

        // 从认证中获取当前的信息
        User user = (User) principals.getPrimaryPrincipal();

        // 从数据库查询当前用户的角色列表,并装载到资源授权器里
        List<Role> roles = roleService.getRolesByUserId(user.getId());
        roles.stream().forEach(item -> {
            authorization.addRole(item.getRoleName());
//            authorization.addStringPermission(item.getRoleName());

            // 再去查询每个角色拥有的资源列表,并装载到资源授权器里
            List<Resource> resources = resourceService.getResourcesByRoleId(item.getId());
            resources.stream().forEach(it -> {
                authorization.addStringPermission(it.getPermission());
                System.out.println(it.getPermission());

            });
        });

        return authorization;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        // 获取用户名
        String userName = (String) token.getPrincipal();

        // 通过用户名查找数据库的 user 信息
        User user = userService.getUserByUserName(userName);
        if (user == null) {
            throw new UnknownAccountException("User name is not exit.");
        }

        // 封装身份验证器
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

ShiroConfig.java

package com.feiyu.sprtingboot.config.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
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.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @description:com.feiyu.sprtingboot.config.shiro_sprtingboot
 * @author: 霏宇
 * @time: 2022/8/31,10:21
 */
@Configuration
public class ShiroConfig {
    @Autowired
    private MyRealm myRealm;

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);

        return securityManager;
    }

    /**
     * anon:匿名访问,无需登录 ---- AnonymousFilter
     * authc:登录后才能访问 ---- FormAuthenticationFilter
     * user:登录过能访问 ---- UserFilter
     * logout:登出 ---- LogoutFilter
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        // 注入安全管理器
        filterFactory.setSecurityManager(securityManager());

        // 设置登录页面、登录成功页面
        filterFactory.setLoginUrl("/login");
        filterFactory.setSuccessUrl("/test/thymeleafTest");

        // 设置其余地址的访问策略
        Map<String, String> filterMap = new LinkedHashMap<>();
        // 匿名策略
        // 登录注册
        filterMap.put("/login", "anon");
        filterMap.put("/register", "anon");
        // 静态资源
        filterMap.put("/favicon.ico", "anon");
        filterMap.put("/css/**", "anon");
        filterMap.put("/images/**", "anon");
        filterMap.put("/js/**", "anon");
        filterMap.put("/vendors/**", "anon");
        filterMap.put("/static/**", "anon");
        // 测试模块
        filterMap.put("/test/**", "anon");
        // api
        filterMap.put("/api/**", "anon");

        // 非匿名策略
        filterMap.put("/**", "authc");
        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }

    /**
     * - 注册shiro方言,让 thymeleaf 支持 shiro 标签
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    @Bean(name="lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * - 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }




}

如果需要加入rememberMeCookie
/**
     * -- Remember Me Cookie
     */
    @Bean

    public SimpleCookie rememberMeCookie() {

        //这个参数是 cookie 的名称

        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

        //如果 httyOnly 设置为 true,则客户端不会暴露给客户端脚本代码,

        //使用 HttpOnly cookie 有助于减少某些类型的跨站点脚本攻击;

        simpleCookie.setHttpOnly(true);

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

        simpleCookie.setMaxAge(1 * 24 * 60 * 60);

        return simpleCookie;

    }

   /**
     * -- 管理器
     */

    @Bean
    public CookieRememberMeManager rememberMeManager() {

        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();

        cookieRememberMeManager.setCookie(rememberMeCookie());

        byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g==");

        cookieRememberMeManager.setCipherService(new AesCipherService());

        cookieRememberMeManager.setCipherKey(cipherKey);

        return cookieRememberMeManager;
    }

    /**

     * sessionCookie

     */

    @Bean
    public SimpleCookie sessionCookie() {

        SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion");

        simpleCookie.setPath("/");

        simpleCookie.setHttpOnly(true);

        simpleCookie.setMaxAge(1 * 24 * 60 * 60);

        return simpleCookie;

    }DefaultWebSessionManager 加入
     /**
     * DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器
     Shiro 默认 Cookie 名称是 JSESSIONID,与 Tomcat 等默认JSESSIONID 冲突,我们需要为 
      Shiro 指定一个不同名称的 Session id,否则抛出 UnknownSessionException: There is no session with id 异常
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    
         sessionManager.setSessionIdCookie(sessionCookie());
    
        advisorAutoProxyCreator.setProxyTargetClass(true);
       // 相隔多久检查一次 session 的有效性
       // sessionManager.setSessionValidationInterval(1 * 24 * 60 * 60 * 1000);
       // session 有效时间
      // sessionManager.setGlobalSessionTimeout(1 * 24 * 60 * 60 * 1000);
        return advisorAutoProxyCreator;
    }SecurityManager 加入
     @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setRememberMeManager(rememberMeManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

身份验证

UserServiceImpl

@Override
    public Result<User> login(User user) {

        // 得到 subject
        Subject subject = SecurityUtils.getSubject();

        // 封装一个登录令牌(装载用户名和密码)
        UsernamePasswordToken token = new UsernamePasswordToken(
                user.getUserName(),
                MD5Util.encode(user.getPassword()));

        try {
            // subject.login()
            subject.login(token);
            subject.checkRoles();
            // 获取当前用户,并将之设置到 session 中
            User temp = (User) subject.getPrincipal();
            Session session = subject.getSession();
            session.setAttribute("user", temp);

            return new Result<User>(Result.ResultStatus.SUCCESS.code, "Success", temp);
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.debug(e.getMessage());
            return new Result<User>(Result.ResultStatus.FAILD.code, e.getMessage());
        }

    }
Role 配合页面标签,控制不同用户访问不同页面
 /**
     * 127.0.0.1/account/users---- get
     */
    @RequiresRoles(value = {"admin","manager"},logical = Logical.OR)
    @GetMapping(value = "/account/users")
    public String userPage(ModelMap modelMap) {
        modelMap.addAttribute("template",
                              "account/users");
        return "index";
    }
Resource 配合控制器注解,控制某个特殊的授权权限
 @RequiresPermissions("/api/user/{id}")
    public Result<Object> deleteUserById(@PathVariable int id){
        return userService.deleteUserById(id);

    }

Html页面

 <ul class="nav child_menu">
                <li shiro:hasAnyRoles="admin,manager,staff"><a href="javascript:void(0);">Profile</a></li>
                <li shiro:hasAnyRoles="admin,manager"><a href="/account/users">Users</a></li>
                <li shiro:hasAnyRoles="admin,manager"><a href="/account/roles">Roles</a></li>
                <li shiro:hasAnyRoles="admin,manager"><a href="/account/resources">Resources</a></li>
     </ul>


 <ul class="nav child_menu">
                <shiro:hasPermission name="/test/thymeleafTest">
                    <li shiro:hasAnyRoles="admin,manager,staff">
                        <a href="/test/thymeleafTest">ThymeleafTest</a></li>
                </shiro:hasPermission>
                <shiro:hasPermission name="/test/vueTest">://用户需要拥有某资源;进行显示
                    <li shiro:hasAnyRoles="admin,manager,staff"><a href="/test/vueTest">VueTest</a></li>
                </shiro:hasPermission>
                <li shiro:hasAnyRoles="admin,manager,staff">
                    <a>二级菜单<span class="fa fa-chevron-down"></span></a>
                    <ul class="nav child_menu">
                        <li class="sub_menu"><a href="javascript:void(0);">三级菜单一</a></li>
                        <li><a href="javascript:void(0);">三级菜单二</a></li>
                    </ul>
                </li>
            </ul>

在这里插入图片描述

部分转载于http://www.sfac.xyz:8000/notes/Java/Apache_Shiro.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个快速开发框架,它提供了一系列的工具和插件,可以快速构建一个企业级的应用程序。而 Shiro 是一个强大而灵活的安全框架,可以提供身份验证、授权、密码加密、会话管理等功能。CAS 是一个单点登录(SSO)协议,可以实现用户在多个应用系统中使用同一个身份验证。 下面是一个简单的 Spring Boot + Shiro + CAS 的示例应用程序: 1. 创建一个 Spring Boot 应用程序,并添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.6.0</version> </dependency> ``` 2. 配置 Shiro: ```java @Configuration public class ShiroConfig { @Bean public CasRealm casRealm() { CasRealm realm = new CasRealm(); realm.setCasServerUrlPrefix("https://cas.example.com/cas"); realm.setCasService("https://myapp.example.com/cas"); realm.setDefaultRoles("user"); realm.setRoleAttributeNames("memberOf"); return realm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(casRealm()); return manager; } @Bean public ShiroFilterFactoryBean shiroFilter() { ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); filter.setSecurityManager(securityManager()); filter.setLoginUrl("https://cas.example.com/cas/login?service=https://myapp.example.com/cas"); filter.setSuccessUrl("/home"); filter.setUnauthorizedUrl("/403"); filter.setFilterChainDefinitionMap(Collections.singletonMap("/**", "authc")); return filter; } } ``` 3. 配置 CAS: ```java @Configuration public class CasConfig { @Bean public CasAuthenticationFilter casAuthenticationFilter() { CasAuthenticationFilter filter = new CasAuthenticationFilter(); filter.setCasServerLoginUrl("https://cas.example.com/cas/login"); filter.setServerName("https://myapp.example.com/cas"); filter.setAuthenticationSuccessHandler(authenticationSuccessHandler()); filter.setAuthenticationFailureHandler(authenticationFailureHandler()); return filter; } @Bean public SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() { SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler(); handler.setDefaultTargetUrl("/home"); handler.setAlwaysUseDefaultTargetUrl(true); return handler; } @Bean public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler(); handler.setDefaultFailureUrl("/login?error=true"); return handler; } @Bean public FilterRegistrationBean<CasAuthenticationFilter> casFilterRegistrationBean() { FilterRegistrationBean<CasAuthenticationFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(casAuthenticationFilter()); registration.addUrlPatterns("/*"); registration.setName("CAS Authentication Filter"); registration.setOrder(1); return registration; } @Bean public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() { ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> registration = new ServletListenerRegistrationBean<>(); registration.setListener(new SingleSignOutHttpSessionListener()); registration.setOrder(2); return registration; } @Bean public ServletRegistrationBean<Servlet> casValidationServletRegistrationBean() { ServletRegistrationBean<Servlet> registration = new ServletRegistrationBean<>(); registration.setServlet(new Cas20ProxyReceivingTicketValidationFilter()); registration.addUrlMappings("/cas/*"); registration.setName("CAS Validation Filter"); registration.setOrder(3); return registration; } } ``` 4. 创建一个 HomeController: ```java @Controller public class HomeController { @GetMapping("/home") public String home() { return "home"; } @GetMapping("/403") public String error403() { return "403"; } } ``` 5. 创建一个 CAS 认证服务器: ```java @SpringBootApplication public class CasServerApplication { public static void main(String[] args) { SpringApplication.run(CasServerApplication.class, args); } } ``` 6. 创建一个 CAS 客户端: ```java @SpringBootApplication @EnableScheduling public class CasClientApplication { public static void main(String[] args) { SpringApplication.run(CasClientApplication.class, args); } } ``` 这就是一个简单的 Spring Boot + Shiro + CAS 的示例应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值