shiro讲解之 SpringMVC 集成Shiro

不念不忘少年蓝@ 2022-06-06 01:26 464阅读 0赞

shiro讲解之 SpringMVC 集成Shiro

本章节将通过实例来学习下SpringMVC+Spring+Shiro如何集成并用一个精简的例子说明。


整合

  • 新建一个完整的Spring+SpringMVC 框架

    • 关于Spring整个SpringMVC的例子已在SpingMVC模块有分享,可移步至SpringMVC。
  • 项目目录

    • 这里写图片描述
  • 整合 Shiro 步骤

    • 下载 Shiro 及相关jar包

      • pom.xml新增以下依赖

        1. <!-- Shiro -->
        2. <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
        3. <dependency>
        4. <groupId>org.apache.shiro</groupId>
        5. <artifactId>shiro-all</artifactId>
        6. <version>1.3.2</version>
        7. </dependency>
        8. <!-- Shiro end -->
        9. <!-- Ehcache -->
        10. <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
        11. <dependency>
        12. <groupId>org.apache.shiro</groupId>
        13. <artifactId>shiro-ehcache</artifactId>
        14. <version>1.2.4</version>
        15. </dependency>
        16. <!-- Ehcahche end -->
    • 配置web.xml

      • Shiro web.xml配置信息参考 Shiro Web App。这里我们的整合将直接参考Spring Example中的web.xml的配置信息。

        1. <!-- 配置Shiro Filter -->
        2. <!-- Shiro Filter is defined in the spring application context: -->
        3. <filter>
        4. <filter-name>shiroFilter</filter-name>
        5. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        6. <init-param>
        7. <param-name>targetFilterLifecycle</param-name>
        8. <param-value>true</param-value>
        9. </init-param>
        10. </filter>
        11. <filter-mapping>
        12. <filter-name>shiroFilter</filter-name>
        13. <url-pattern>/*</url-pattern>
        14. </filter-mapping>

        web.xml文件中配置Shiro 的DelegatingFilterProxy,该Filter的核心作用点就是用户拦截和过滤请求的URL,之于如何处理URL将在Shiro配置文件的 shiroFilter中有具体的说明。

    • 新建ehcache.xml缓存
      在整合Shiro的过程中我们在使用shiro的缓存机制时会使用到缓存,这一点在pom.xml的依赖有提现,另外在后续的篇章中我们也将详细说明Shiro的缓存机制,这里做初步了解。
      在classpath(一般建议,也可以自定义)下新建一个ehcache.xml,配置信息如下,如有必要可全部复制

      1. <?xml version="1.0" encoding="UTF-8"?>
      2. <ehcache>
      3. <diskStore path="java.io.tmpdir" /> <!-- 缓存存放目录(此目录为放入系统默认缓存目录),也可以是”D:/cache“ java.io.tmpdir -->
  1. <!-- 登录记录缓存 锁定10分钟 -->
  2. <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true">
  3. </cache>
  4. <cache name="authorizationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true">
  5. </cache>
  6. <cache name="authenticationCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true">
  7. </cache>
  8. <cache name="shiro-activeSessionCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true">
  9. </cache>
  10. <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
  11. <!-- name:Cache的唯一标识 maxElementsInMemory:内存中最大缓存对象数 maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大 eternal:Element是否永久有效,一但设置了,timeout将不起作用 overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中 timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大 timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大 diskPersistent:是否缓存虚拟机重启期数据 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用) -->
  12. </ehcache>
  13. * 在ApplicationContext.xml中配置Shiro相关信息
  14. 在ApplicationContext.xml文件中配置Shiro的必要信息,这里我们的整合将直接参考Spring Example中的applicationContext.xml的配置信息。基本配置如下(在讲解后续功能时会不断完善该配置)
  15. <!-- 1.配置SecurityManager -->
  16. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  17. <property name="cacheManager" ref="cacheManager" />
  18. <!-- Shiro 多Realm认证策略 -->
  19. <!-- <property name="authenticator" ref="authenticator"></property> -->
  20. <property name="realms">
  21. <list>
  22. <ref bean="shiroRealm"></ref>
  23. </list>
  24. </property>
  25. </bean>
  26. <!-- 2.配置 cacheManager -->
  27. <!-- 2.1 自定义ehcache 此处需要加入ehcache jar 及其配置文件 -->
  28. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
  29. <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
  30. </bean>
  31. <!-- 3.配置Realm -->
  32. <bean id="shiroRealm" class="com.shiro.example.interceptor.realm.ShiroRealm">
  33. <!-- <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="1024"></property> </bean> </property> -->
  34. </bean>
  35. <!-- <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> 多个Reaml认证中有一个认证成功即成功策略 <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> 第一个Reaml认证策略 <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean> 必须所有Reaml都成功认证策略 <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean> </property> </bean> -->
  36. <!-- 4.配置lifecycleBeanPostProcessor,可以自动的调用Spring IOC 容器中shiro bean的生命周期方法 -->
  37. <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
  38. <!-- 5.启用ICO 容器中使用Shiro 注解,但必须在配置了lifecycleBeanPostProcessor之后方可使用 -->
  39. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
  40. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
  41. <property name="securityManager" ref="securityManager" />
  42. </bean>
  43. <!-- 6.配置ShiroFilter 6.1 id必须和web.xml中的DelegatingFilterProxy的 FilterName一致 -->
  44. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  45. <property name="securityManager" ref="securityManager" />
  46. <property name="loginUrl" value="/login/toLogin" />
  47. <property name="successUrl" value="/example/index" />
  48. <property name="unauthorizedUrl" value="/example/unauthorized" />
  49. <property name="filterChainDefinitions">
  50. <value>
  51. /login/toLogin = anon
  52. /login/loginVal = anon
  53. /login/logout = logout
  54. /** = authc
  55. </value>
  56. </property>
  57. </bean>

一个Shiro例子

以上配置好以后我们将做一个Shiro 认证的登录例子。

  • 新建Form表单,本例子中的login.jsp

    1. <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
    2. <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path; %>
    3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    4. <html>
    5. <head>
    6. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    7. <title>Insert title here</title>
    8. </head>
    9. <body>
  1. <form action="<%=basePath %>/login/loginVal" method="POST">
  2. username: <input type="text" name="username" /> <br> <br>
  3. password: <input type="password" name="password" /> <br> <br>
  4. <input type="submit" value="Submit" />
  5. </form>
  6. </body>
  7. </html>
  • 实现Controller方法

    1. package com.shiro.example.controller;
    2. import org.apache.shiro.SecurityUtils;
    3. import org.apache.shiro.authc.UsernamePasswordToken;
    4. import org.apache.shiro.subject.Subject;
    5. import org.springframework.stereotype.Controller;
    6. import org.springframework.web.bind.annotation.RequestMapping;
    7. import org.springframework.web.bind.annotation.RequestParam;
    8. @Controller
    9. @RequestMapping(value = "/login")
    10. public class LoginProcess {
    11. @RequestMapping("/loginVal")
    12. public String login(@RequestParam("username") String username, @RequestParam("password") String password)
    13. throws Exception {
    14. System.out.println("username: " + username + "+ password: " + password);
    15. Subject subject = SecurityUtils.getSubject();
    16. // 如果当前用户并未经过认证登录的
    17. /*if (!subject.isAuthenticated()) {*/
    18. UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    19. token.setRememberMe(true);
    20. try {
    21. subject.login(token);
    22. } catch (Exception e) {
    23. System.out.println("登录失败: " + e.getMessage());
    24. return "erro";
    25. }
    26. /*} System.out.println("用户尚未被认证");*/
    27. return "index";
    28. }
    29. @RequestMapping("/toLogin")
    30. public String toLogin() throws Exception {
    31. return "login";
    32. }
    33. }
  • 实现自定义Realm方法

    1. package com.shiro.example.interceptor.realm;
    2. import java.util.HashSet;
    3. import java.util.Set;
    4. import org.apache.shiro.authc.AuthenticationException;
    5. import org.apache.shiro.authc.AuthenticationInfo;
    6. import org.apache.shiro.authc.AuthenticationToken;
    7. import org.apache.shiro.authc.LockedAccountException;
    8. import org.apache.shiro.authc.SimpleAuthenticationInfo;
    9. import org.apache.shiro.authc.UnknownAccountException;
    10. import org.apache.shiro.authc.UsernamePasswordToken;
    11. import org.apache.shiro.authz.AuthorizationInfo;
    12. import org.apache.shiro.authz.SimpleAuthorizationInfo;
    13. import org.apache.shiro.crypto.hash.SimpleHash;
    14. import org.apache.shiro.realm.AuthorizingRealm;
    15. import org.apache.shiro.subject.PrincipalCollection;
    16. import org.apache.shiro.util.ByteSource;
    17. import com.shiro.example.entity.SubjectEntity;
    18. public class ShiroRealm extends AuthorizingRealm {
    19. @Override
    20. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    21. System.out.println("[FirstRealm] doGetAuthenticationInfo");
    22. // 1. 把 AuthenticationToken 转换为 UsernamePasswordToken
    23. UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    24. // 2. 从 UsernamePasswordToken 中来获取 username
    25. String username = upToken.getUsername();
    26. // 3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
    27. SubjectEntity principals = new SubjectEntity("123456", "Dustyone");
    28. // 4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
    29. if ("unknown".equals(username)) {
    30. throw new UnknownAccountException("用户不存在!");
    31. }
    32. // 5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.
    33. if ("monster".equals(username)) {
    34. throw new LockedAccountException("用户被锁定");
    35. }
    36. // 6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为:
    37. // SimpleAuthenticationInfo
    38. // 以下信息是从数据库中获取的.
    39. // 1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
    40. Object principal = principals.getUsername();
    41. // 2). credentials: 密码.
    42. Object credentials = principals.getPassword(); // "fc1709d0a95a6be30bc5926fdb7f22f4";
  1. // 3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
  2. String realmName = getName();
  3. SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
  4. /* * //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String * realmName = getName(); //4). 盐值. ByteSource credentialsSalt = * ByteSource.Util.bytes(username); * * SimpleAuthenticationInfo info = null; //new * SimpleAuthenticationInfo(principal, credentials, realmName); info = * new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, * realmName); */
  5. return info;
  6. }
  • 新建Subject Entity

    1. package com.shiro.example.entity;
    2. import java.io.Serializable;
    3. public class SubjectEntity implements Serializable{
    4. /** * Subject Entity */
    5. private static final long serialVersionUID = 1L;
    6. public SubjectEntity() {
    7. super();
    8. }
    9. @Override
    10. public String toString() {
    11. return "SubjectEntity [username=" + username + ", password=" + password + "]";
    12. }
    13. public SubjectEntity(String password, String username) {
    14. super();
    15. this.username = username;
    16. this.password = password;
    17. }
    18. public String getUsername() {
    19. return username;
    20. }
    21. public void setUsername(String username) {
    22. this.username = username;
    23. }
    24. public String getPassword() {
    25. return password;
    26. }
    27. public void setPassword(String password) {
    28. this.password = password;
    29. }
    30. private String username;
    31. private String password;
    32. }
  • Example 文件

    1. ```
    2. package com.shiro.example.controller;
    3. import org.springframework.stereotype.Controller;
    4. import org.springframework.web.bind.annotation.RequestMapping;
    5. @Controller
    6. @RequestMapping("/example")
    7. public class Example {
    8. @RequestMapping(value = "/index")
    9. public String example() throws Exception {
    10. return "example";
    11. }
    12. @RequestMapping(value="/unauthorized")
    13. public String unauthorized(){
    14. return "unauthorized";
    15. }
    16. }
    17. ```

本例子涉及的核心功能点

  • Shiro 认证(登录验证)

    • 由我们自定义的Realm代码中可知我们指定登录的Subject的usernam=Dustyone password=123456。唯有此principal(实体)组合才能通过Shiro认证(暂时不涉及加密)。
    • 正确登录
      这里写图片描述

      这里写图片描述

    • 错误principal登录

      这里写图片描述

      这里写图片描述

  • Shiro 授权

    • 在shiroFilter的 filterChainDefinitions我们做了访问权限声明即除了 /login/toLogin/login/loginVal 可以被匿名访问意外其他的任何url请都将被 shiro filetr收集并进行Subject 授权(即用户是否有权限访问**, 此处的两个星号标识资源。)。以 /example/index请求为例,在用户未登录的情况下访问该路径将直接跳转至登录页面。只有在登录成功的情况下才能访问 /example/index
    • 未登录的情况下访问 /example/index 将直接跳至登录页面
      这里写图片描述
    • 已登录的情况下访问 /example/index 直接可以访问该资源
      这里写图片描述

      这里写图片描述

  • Shiro 缓存
    用户登录成功之后(特质在启用了shiro的缓存机制之后),shiro 缓存将对最近一个Subject的认证或授权信息写入缓存。当该Subject 再次访问需要认证或授权的资源时,shiro将从缓存中(本例子中特指ehcache.xml)读取Subject的相关信息而不再此对该Subject进行认证或授权处理(这里的认证或授权特指我们自定义的Reaml中的 doGetAuthenticationInfo 和 doGetAuthorizationInfo 两个方法)。
  • Shiro 登出
    在访问logout 之后shiro将清除所有缓存信息并跳至登录页面。

发表评论

表情:
评论列表 (有 0 条评论,464人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringMVC集成shiro

    最近公司一个老的后台管理系统需要加权限验证(前端使用easyUI),经过各种踩坑,终于是完成了,话不多说,直接进入主题 数据库结构 用户表(使用的项目本身已经在用的表)