• Spring Boot 之 SpringSecurity、Shiro


    安全框架

    • 安全框架:对访问权限进行控制
      • 安全性包括用户认证(Authentication)和用户授权(Authorization)两部分
    • 用户认证:指验证某个用户是否为系统中的合法主体
      • 即用户能否访问该系统
      • 一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程
    • 用户授权:指验证某个用户是否有权限执行某个操作
      • 不同用户所具有的权限是不同的
      • 一般系统为不同的用户分配不同的角色,而每个角色则对应一系列的权限

    SpringSecurity

    • Spring Security:在架构上将认证与授权分离,并提供了扩展点
      • 充分利用 Spring IoC(控制反转),DI(依赖注入)和 AOP(面向切面编程)功能
      • 为应用系统提供声明式的安全访问控制功能,减少了为系统安全控制编写大量重复代码的工作
      • 确保基于 Spring 的应用程序提供身份验证和授权支持
      • Spring MVC 有很好地集成 ,并配备了流行的安全算法实现捆绑在一起
    • 对 Web 资源进行保护,最好于 Filter;对方法调用进行保护,最好于 AOP
      • Spring Security 在进行用户认证以及授予权限时通过各种各样的拦截器来控制权限的访问,从而实现安全
    特点
    • Spring SecurityWeb 安全性的支持大量地依赖于 Servlet 过滤器
      • 过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理
        • Spring Security 提供有若干个过滤器,能够拦截 Servlet 请求
          • 并将请求转给认证和访问决策管理器处理,从而增强安全性
        • 根据需要使用适当的过滤器来保护自己的应用程序
      • 要使用 Servlet 过滤器且令其正常工作必须在 web.xml 文件中使用 元素配置
        • 但并不适用于使用依赖注入进行的配置
    • FilterToBeanProxy 是特殊的Servlet过滤器
      • 将委托转发给 Spring 应用程序上下文中的一个 Bean 来完成
        • 被委托的 Bean 几乎和 Servlet 过滤器一样
          • 实现 javax.servlet.Filter 接口
        • 但在 Spring 配置文件非 web.xml 文件中配置
      • FilterToBeanProxy 代理给的 Bean 可以是 javax.servlet.Filter 的任意实现
        • 可以是 Spring Security 的任何过滤器
          • 或自己创建的一个过滤器
        • 但是 Spring Security 要求至少配置四个而且可能一打或者更多的过滤器
    • 除了不能脱离Springshiro 的功能都有
      • Spring SecurityOauthOpenID 也有支持
        • Shiro 则需要手动实现
        • 现租户与各个产品间单点登录已经通过 cookies 实现
          • Spring Security的这两个功能可以不考虑。
      • Spring Security 的权限细粒度更高

    注:

    • OAuth 在客户端与服务提供商之间,设置了一个授权层(authorization layer)
      • 客户端不能直接登录服务提供商,只能登录授权层,将用户与客户端区分开
      • 客户端登录授权层所用的令牌(token),与用户的密码不同
        • 用户可在登录的时候指定授权层令牌的权限范围和有效期
      • 客户端登录授权层
        • 服务提供商根据令牌的权限范围和有效期向客户端开放用户储存的资料
    • OpenID 系统的第一部分是身份验证
      • 即通过 URI 来认证用户身份
        • 目前的网站依靠用户名和密码来登录认证
      • 使用 OpenID ,网站地址(URI)就是用户名
        • 密码安全的存储在一个 OpenID 服务网站上
    配置
    • 添加相应依赖信息:包括 Thymeleaf 整合 security 依赖

      • 
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        
        <dependency>
            <groupId>org.thymeleaf.extrasgroupId>
            <artifactId>thymeleaf-extras-springsecurity5artifactId>
        dependency>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
    1. 导入 Spring Security 依赖
    2. 创建配置类继承 WebSecurityConfigurerAdapter
      • 重写方法完成认证与授权
      • WebSecurityConfigurerAdapter:自定义 Security 策略
      • AuthenticationManagerBuilder:自定义认证策略
      • @EnableWebSecurity:开启 WebSecurity 模式
    3. 在 页面中添加相应请求或方法验证权限
    请求拦截
    配置类
    • 配置需要成对出现,并且配置的顺序也很重要
      • 声明在前面的规则拥有更高的优先级
    // AOP 实现
    @EnableWebSecurity
    public class SecurityConf extends WebSecurityConfigurerAdapter {
    
        // 请求授权
        @Override 
        protected void configure(HttpSecurity http) throws Exception {
            // 链式编写:不同模块使用 and() 连接,也可以分开写
            // 对请求授权 
            http.authorizeRequests()			
                	// 不需要通过登录验证就可以被访问的资源路径
                	.antMatchers("/", "/index", "/toLogin").permitAll()
                	// 需要对应权限、角色访问
                	.antMatchers("/level1/**").hasRole("vip1")
                	.antMatchers("/level2/**").hasRole("vip2")
                	.antMatchers("/level3/**").hasRole("vip3")
                	// 上面的任何请求都必须经过身份验证
                	.anyRequest().authenticated();
             // 表单登录,用户未登录时,访问任何资源都转跳到该路径,即登录页面
    		http.formLogin()
                	// 登录页面,设置为自定义的登录页面
                	.loginPage("/toLogin")
                	// 登录表单中 action 的地址,即实际处理认证请求的路径
                	.loginProcessingUrl("/login")
                	// 获取用户名输入框参数:默认 username
                	.usernameParameter("username")
                	// 获取密码输入框参数:默认 password
                	.passwordParameter("password")
                	// 登录认证成功后默认转跳的路径,登陆失败后返回原页面
                	.successForwardUrl("/").failureForwardUrl("/toLogin")
                	.permitAll()
    		// 注销
    		http.logout()
                	// 登出成功后返回的请求
               		.logoutSuccessUrl("/")
                	.permitAll()
    		// 关闭 csrf 功能:跨站请求伪造,默认只能通过 post 方式提交请求
             http.csrf().disable()
                // 记住我功能,使用 Cookie 缓存,默认有效期14天
                .rememberMe().rememberMeParameter("remember");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • ?:匹配任何单字符
    • *:匹配 0 或者任意数量的字符
    • **:匹配 0 或更多目录
    常用方法
    • permitAll():允许任何访问
    • denyAll():拒绝所有访问
    • anonymous():允许匿名用户访问
    • authenticated():允许认证的用户进行访问
    • hasRole(String)
      • 用户具备给定角色、用户组 允许访问
    • hasAnyRole(String…)
      • 用户具有给定角色、用户组 中的一个允许访问
    • hasAuthority(String)
      • 用户具备给定权限允许访问
    • hasAnyAuthority(String…)
      • 用户具备给定权限中的某一个允许访问
    • principal
      • 允许直接访问代表当前用户的主体对象
    • rememberMe()
      • 用户是通过Remember-me功能认证允许访问
    • fullyAuthenticated()
      • 如果用户是完整认证允许访问
      • 不是通过Remember-me功能认证的
    • hasIpAddress(String)
      • 请求来自给定ip地址允许访问
    • isAnonymous():当前委托人是否为匿名用户
    • isRememberMe():当前主体是否是“记住我”的用户
    • not() :对其他访问结果求反
    权限认证
    • 权限认证和授权在同一配置类中
    内存认证
    • AuthenticationManagerBuilder:使用构造者方式来构建
      • 先调用 inMemoryAuthentication()
        • 指定用户存储在内存中
      • 调用了 passwordEncoder()
        • 指定认证密码的加密方式
        • Spring security5后,必须指定加密方式,不然程序会报错
      • 调用的 withUser()、password()、authorities()
        • 指定用户的账号、密码以及权限名
        • 添加完一个用户后,使用 and() 方法来连接下一个用户的添加
    • 这种配置在修改用户时必须修改代码
      • 甚至无法注册
    @EnableWebSecurity
    public class SecurityConf extends WebSecurityConfigurerAdapter {
        // 权限认证
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 在内存里面存储用户的身份认证和授权信息
            auth.inMemoryAuthentication()	
                // 添加用户权限:用户名、密码(加密)、角色信息    
                .withUser("user").password(passwordEncoder().encode("123456")).roles("vip1")
                // 添加多个用户使用 and()
                .and()
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
                // 配置BCrypt加密
                .and().passwordEncoder(passwordEncoder()); 
        }
    
        // 加密对象,密码需要加密才能安全保存
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    JDBC 认证
    • 将用户信息存储在数据库中
      • 方便对用户信息进行增删改查
      • 还可以添加除认证信息外的附加信息
    • 调用 jdbcAuthentication()
      • 指定使用 jdbc 的方式来查询用户和权限
    • dataSource():指定数据库连接信息
    • passwordEncoder():指定密码加密规则
      • 用户的密码数据应该以同样的方式进行加密存储
        • 否则两个加密方式不同无法匹配
    • usersByUsernameQuery()authoritiesByUsernameQuery()
      • 定义了查询用户和权限信息的 sql 语句
      • Spring security 默认了查询用户、权限甚至还有群组用户授权的sql
        • 三条默认的 sql 存放在 org......userdetails.jdbc.JdbcDaoImpl
        • 使用默认的,那表中关键性的字段必须和语句中的一致
    // 自动装配数据源对象
    @Autowired
    private DataSource dataSource;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 使用 JDBC 存储身份认证信息
        auth.jdbcAuthentication()
            // 传入数据源
            .dataSource(dataSource)
            // 指定密码加密方式
            .passwordEncoder(passwordEncoder())
            // 根据用户名获取用户信息
            .usersByUsernameQuery("select username, password, status from Users where username = ?")
            // 根据用户名获取认证权限
            .authoritiesByUsernameQuery("select username, authority from Authority where username = ?");
    }
    
    //定义加密对象
    @Bean  
    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    LDAP用户存储
    • LDAP:轻型目录访问协议,一个开放的,中立的,工业标准的应用协议
      • 通过IP协议提供访问控制和维护分布式信息的目录信息
      • 即:将用户信息存放在另外一台服务器中
    • userSearchFilter()groupSearchFilter()
      • 设置用户和群组的过滤条件
    • userSearchBase()groupSearchBase()
      • 设置了搜索起始位置
    • contextSource().url()
      • 设置LDAP服务器的地址
    • 没有远程的服务器可以使用 contextSource().root() 使用嵌入式 LDAP 服务器
      • 将使用项目中的用户数据文件来提供认证服务
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer =                     auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}");
    
        configurer.passwordCompare()
            .passwordEncoder(passwordEncoder())
            .passwordAttribute("passcode");
        configurer.contextSource().url("ldap://xxxxx.com:33389/dc=xxxxxx,dc=com");
    }
    
    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    MyBatis

    自定义认证方式:不仅限于 MyBatis 使用

    • 自行使用认证名称来查找对应的用户数据
      • 然后交给 Spring Security 使用
    • 需要定义一个实现 UserDetailsServiceservice
      • 得到用户对象
    UserServce
    • 只需要实现一个方法:loadUserByUsername()
      • 使用传过来的 username 来匹配带有密码等信息的用户实体
        • User 类需要实现 UserDetails
        • 这是 Security 中的 user 类,而非自定义 User 类
      • 即:查到的信息里必须得有 Spring Security 所需要的信息
    • 类中可以注入 mapper 等等查用户
      • 如果查不到,留在这个页面
      • 如果查到了,做出一定逻辑(例如判空等等)
        • 把用户信息封装到 Spring SecurityUser类中
          • 区分自定义自定义 UserSpring SecurityUser
        • Spring Security 拿前台的数据比较,做出操作
          • 例如:认证成功或者错误
    @Service
    public class UserService implements UserDetailsService {
    
        @Qualifier("userDao")
        @Autowired
        private UserDao userDao;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (username == null){
                throw new UsernameNotFoundException("用户名不存在");
            }
            // 获取用户对象
            security.pojo.User user = userDao.getUserByName(username);
            List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
            // 得到用户权限
            String role = user.getRoles();
            // 权限不为空放到到 List 集合中
            if (role != null && !"".equals(role)){
                authorityList.add(new SimpleGrantedAuthority(role.trim()));
            }
            // 封装到框架的 USer 类中并提交
            return new User(user.getUsername(),user.getPassword(),authorityList);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    SecurityConf
    • 指定 Spring Security 自定义的 UserDetailsService 实现类
      • 会自动去调用 loadUserByUsername() 来查找用户
      • 用户的密码和提交密码加密方式要相同
        • 否则两个加密方式不同无法匹配
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private MyUserDetailsService userDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService)
                //密码加密方式和数据库密码加密方式要相同
                .passwordEncoder(passwordEncoder());
        }
    
        @Bean
        private PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    表单登录
    
    <form th:action="@{/login}" method="post">
        
        <input type="text" placeholder="Username" name="username">
        
        <input type="password" placeholder="PassWord" name="password">
        
        <input type="checkbox" class="ui checkbox bordered" name="remember">保存账号密码
        <input type="submit" value="提交"/>
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    验证状态权限
    • 整合 Thymeleaf 模板使用

    • 命名空间

      <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
      
      • 1
      • 2
    
    <div sec:authorize="!isAuthenticated()"/>  
    
    <div sec:authorize="isAuthenticated()"/>   
    
    <div sec:authorize="hasAnyRole('vip1','vip2','vip3')"/>  
    
    <div sec:authorize="hasRole('vip1')"/>     
    
    <span sec:authentication="principal.authorities">  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    shiro

    • Apache Shiro:一个强大且易用的 Java 安全框架
      • 能够非常清晰的处理身份验证、授权、管理会话以及密码加密
    • 利用其易于理解的 API,可以快速、轻松地获得任何应用程序
      • 从最小的移动应用程序到最大的网络和企业应用程序
    • Shiro:主要分为两个部分:认证和授权
      • Shiro 只是一个框架而已,内容需要自己去构建
      • 前后是自己的,中间是 Shiro 去搭建和配置好的
    核心组件
    • 三个核心组件:SubjectSecurityManagerRealms
    • Subject:当前操作用户
      • Shiro 中,Subject 并不仅仅指人
        • 也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物
        • 仅仅意味着当前跟软件交互的东西
      • Subject 代表当前用户的安全操作,SecurityManager 则管理所有用户的安全操作
    • SecurityManagerShiro 框架的核心,典型的 Facade 模式
      • 通过 SecurityManager 来管理内部组件实例,并提供安全管理的各种服务
    • Realm:充当 Shiro 与应用安全数据间的 桥梁 或 连接器
      • 当对用户执行认证(登录)和授权(访问控制)验证
      • Shiro 从应用配置的 Realm 中查找用户及其权限信息
        • Realm 实质上是安全相关的 DAO
          • 封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro
        • 当配置 Shiro 时,必须至少指定一个 Realm,用于认证和(或)授权
          • 配置多个 Realm 是可以的,但至少需要一个
      • Shiro 内置了可连接大量安全数据源(又名目录)的 Realm
        • LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等
        • 若缺省的 Realm 不能满足需求,可以插入代表自定义数据源的 Realm 实现

    组件

    • UsernamePasswordToken
      • Shiro 用来封装用户登录信息,使用用户的登录信息创建令牌 Token
      • 登录的过程即 Shiro 验证令牌是否具有合法身份以及相关权限
    • AuthenticationInfo:用户的角色信息集合,认证时使用
    • AuthorizationInfo,角色的权限信息集合,授权时使用
    • DefaultWebSecurityManager:安全管理器
      • 自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效
    • ShiroFilterFactoryBean:过滤器工厂
      • Shiro 的基本运行机制是开发者定制规则,Shiro 去执行
      • ShiroFilterFactoryBean 创建 Filter 对象来完成

    功能模块

    • Session Manager:会话管理,用户登录后的session相关管理
    • Cryptography:加密,密码加密等
    • Web Support:Web支持,集成Web环境
    • Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中
    • Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去
    • Testing:测试支持
    • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
    • Remember Me:记住我

    在这里插入图片描述

    特点
    • 易于理解的 Java Security API
    • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等)
    • 对角色的简单的签权(访问控制),支持细粒度的签权
    • 支持一级缓存,以提升应用程序的性能
    • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境
    • 异构客户端会话访问
    • 非常简单的加密 API
    • 不跟任何的框架或者容器捆绑,可以独立运行
    配置
    • 添加依赖信息:包括模板支持、shiro、日志 以及数据库等其他依赖
    
    <dependency>
        <groupId>com.github.theborakompanionigroupId>
        <artifactId>thymeleaf-extras-shiroartifactId>
        <version>2.1.0version>
    dependency>
    
    
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-spring-boot-web-starterartifactId>
        <version>1.8.0version>
    dependency>
    
    
    <dependency>
        <groupId>org.apache.logging.log4jgroupId>
        <artifactId>log4j-to-slf4jartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    MyController
    //仅有部分相关方法
    
    // 要登录请求处理:跳转到登录页面
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }
    // 登录处理
    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        // 获取当前用户 subject 对象
        Subject subject = SecurityUtils.getSubject();
        // 使用传进来的用户名、密码创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            // 执行登录操作:由框架进行处理
            // 认证、授权在 realm 中
            // 拦截规则在过滤器中定义
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            // 拦截异常情况
            model.addAttribute("message", "用户名不存在");
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("message", "密码错误");
            return "login";
        }
    
    }
    // 认证异常请求处理
    @RequestMapping("/unauthorized")
    @ResponseBody
    public String unauthorized(){
        return "未获得权限,无法访问";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    Realm
    public class UserRealm extends AuthorizingRealm {
    
        // 注入对象从数据库中查找用户信息
        @Autowired
        private UserDao userDao;
        
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // 得到认证时传入的用户对象(或用户名、id 等对象)
            User user = (User)principalCollection.getPrimaryPrincipal();
            
            // 或得到 Subject 当前用户对象
    		// Subject subject = SecurityUtils.getSubject();
            // 转为自定义的 User 对象
            // User user = (User)subject.getPrincipal();
            
            // 硬授权:所有进入的用户都会授权 user:add
    		// info.addStringPermission("user:add");
            
            // 获取用户角色信息并添加角色(或使用授权)
            info.addRole(user.getRoles());
            return info;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 得到传进来的用户名
            String username = (String) authenticationToken.getPrincipal();
            // 通过用户名查询用户
            User user = userDao.getByName(username);
            // 如果用户为 null
            if (user == null) {
                // 不正确时直接返回 null,会自动捕捉异常
                return null; 
            }
    		// 传入用户对象、密码、自定义 Realm 类名传进去由框架自动匹配
            return  new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            /* 
            第一个参数可以是用户对象、用户名、用户id 等
            可以在授权时获取此时传入的信息,密码会由框架自动匹配
            */
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    ShiroConfig
    • CustomRealmSecurityManager 等注入到 spring 容器
    • 自动装配 3 个 bean 实例
      1. 自定义过滤器 MyRealm
        • 业务逻辑全部定义在这个 bean 中
      2. DefaultWebSecurityManager
        • MyRealm 注入到 DefaultWebSecurityManager 完成注册
      3. ShiroFilterFactoryBean
        • Shiro 自带的一个 Filter 工厂实例
        • 所有的认证和授权判断都是由这个 bean 生成的 Filter 对象来完成的
    • Shiro 框架的运行机制
      • 开发者只需要定义规则,进行配置
      • 具体的执行者全部由 Shiro 自己创建的 Filter 来完成
    @Configuration
    public class ShiroCong {
    
        // Realm 对象,将自定义验证方式加入容器
        @Bean
        public UserRealm userRealm(){
            return new UserRealm();
        }
    
        // DefaultWebSecurityManager: 安全管理器
        // 权限管理,配置主要是 Realm 的管理认证
        @Bean
        public DefaultWebSecurityManager securityManager(){
            // 关联 Realm 对象
            return new DefaultWebSecurityManager(userRealm());
        }
    
        // ShiroFilterFactoryBean:过滤器对象
        // Filter 工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            // 关联 SecurityManager 对象
            bean.setSecurityManager(securityManager());
            // 使用 LinkHashMap 存放路径拦截规则,有序:先配置先生效
            Map<String, String> filterMap = new LinkedHashMap<>();
            // 默认无法配置多角色访问同一权限,需要自定义修改 shiro 过滤器配置
            // 授权: 有 add 角色才能访问 add 请求
            filterMap.put("/user/add", "roles[add]");
            // 授权:必须有 user:update 权限才能访问 update
            filterMap.put("/user/update", "perms[user:update]");
            // 拦截: 对 user下资源必须登陆后才能访问
            filterMap.put("/user/**", "authc");
            // 登出
            filterMap.put("/logout", "logout");
            // 对所有用户认证必须登录后访问
            filterMap.put("/**", "authc");
            // 将设置了请求权限的 Map 集合放到 过滤器对象中
            bean.setFilterChainDefinitionMap(filterMap);
            // 未登录跳转到登录页
            bean.setLoginUrl("/toLogin");
            // 登陆成功请求;跳转首页
            bean.setSuccessUrl("/index");
            // 认证异常请求处理:权限不足
            bean.setUnauthorizedUrl("/unauthorized");
            return bean;
        }
    
        // 配置 ShiroDialect 方言标签整合 Thymeleaf
        @Bean
        public ShiroDialect shiroDialect(){
            return new ShiroDialect();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    Shiro 过滤器

    自定义 Shiro 过滤器

    • 对 URL 进行拦截
      • 没有认证的需要认证
      • 认证成功的则可以根据需要判断角色及权限
    • 过滤器需要自定义
      • 然后指定认证和授权的逻辑
      • 继承抽象类 AuthorizingRealm,实现两个抽象方法分别完成授权和认证的逻辑
    过滤器分类
    • 认证过滤器
      • anon:无需认证即可访问,游客身份
      • authc:必须认证、登录 才能访问
      • authcBasic:需要通过 httpBasic 认证
      • user:不一定已通过认证
        • 只要曾经被 Shiro 记录登录状态的用户就可以正常发起请求
        • 比如 rememberMe
    • 授权过滤器
      • perms:必须拥有对某个资源的访问权限(授权)才能访问
      • role:必须拥有某个角色权限才能访问
      • port:请求的端口必须为指定值才可以访问
      • rest:请求必须是 RESTful
        • method 为 post、get、delete、put
      • ssl:必须是安全的 URL 请求,协议为 HTTPS
    常见shiro异常
    • AuthenticationException:认证异常
      • Shiro 在登录认证过程中,认证失败需要抛出的异常
        • 包含以下子类
      • CredentitalsException:凭证异常
        • IncorrectCredentialsException:不正确的凭证
        • ExpiredCredentialsException:凭证过期
      • AccountException:账号异常
        • ConcurrentAccessException:并发访问异常,多个用户同时登录时抛出
        • UnknownAccountException:未知的账号
        • ExcessiveAttemptsException:认证次数超过限制
        • DisabledAccountException: 禁用的账号
        • LockedAccountException:账号被锁定
        • UnsupportedTokenException:使用了不支持的Token
    • AuthorizationException: 授权异常
      • Shiro在登录认证过程中,授权失败需要抛出的异常
        • 包含以下子类
      • UnauthorizedException
        • 抛出以指示请求的操作或对请求的资源的访问是不允许的
      • UnanthenticatedException
        • 当尚未完成成功认证时,尝试执行授权操作时引发异常
    页面
    DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="Thymeleaf"
          xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
        <head>
            <meta charset="UTF-8" />
            <title>Insert title heretitle>
        head>
        <body>
            <h3>indexh3>
            
            <p shiro:guest="">Please <a href="login.html">logina>p>
            
            <p shiro:user="">
                Welcome back John! Not John? Click <a href="login.html">herea> to login.
            p>
            
            <p shiro:authenticated="">
                Hello, <span shiro:principal="">span>, how are you today?
            p>
            <a shiro:authenticated="" href="updateAccount.html">Update your contact informationa>
            
            <p>Hello, <shiro:principal/>, how are you today?p>
            
            <p shiro:notAuthenticated="">
                Please <a href="login.html">logina> in order to update your credit card information.
            p>
            
            <a shiro:hasRole="admin" href="admin.html">Administer the systema>
            
            <p shiro:lacksRole="developer">
                Sorry, you are not allowed to developer the system.
            p>
            
            <p shiro:hasAllRoles="developer, 2">
                You are a developer and a admin.
            p>
            
            <p shiro:hasAnyRoles="admin, vip, developer,1">
                You are a admin, vip, or developer.
            p>
            
            <a shiro:hasPermission="userInfo:add" href="createUser.html">添加用户a>
            
            <p shiro:lacksPermission="userInfo:del">
                Sorry, you are not allowed to delete user accounts.
            p>
            
            <p shiro:hasAllPermissions="userInfo:view, userInfo:add">
                You can see or add users.
            p>
            
            <p shiro:hasAnyPermissions="userInfo:view, userInfo:del">
                You can see or delete users.
            p>
            <a shiro:hasPermission="pp" href="createUser.html">Create a new Usera>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    Python Opencv实践 - 视频文件操作
    java js 经纬度转换 大地坐标(高斯投影坐标)与经纬度互相转换
    跨平台宏定义
    jenkins安装和配置(二):配置第一个项目
    ElementPlus Switch 开关基础使用
    实现一个通用的函数柯里化的函数
    Python零基础入门-9类
    day52|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组
    HTML学习总结
    大一,小小练习题--含答案
  • 原文地址:https://blog.csdn.net/qq_66991094/article/details/127660279