shiro整合SSM(spring_springmvc_mybatis)


SSM(示例代码码云地址:https://gitee.com/joy521125/ssm-senior-base.git)

一、加入的jar包和目录结构:

  • ehcache-core-2.5.7.jar
  • encoder-1.2.3.jar
  • shiro-all-1.9.0.jar
  • slf4j-api-1.7.32.jar
  • slf4j-log4j12-1.7.12.jar

  

二、整合ssm 的基本配置

shiro.xml配置;

参考地址参考地址 /shiro-root-1.3.2/samples/web/src/main/webapp/WEB-INF/applicationContext.xml;

1.配置securityManager

2.配置缓存管理器 :需要加入ehcache的jar包及配置文件(ehcache.xml 可参考)

3.配置realm :配置了直接实现Realm的接口

4.配置了lifecycleBeanPostProcessor:可以自定义的来调用配置在spring IOC 容器中 shiro bean 的生命周期方法

5.启用IOC容器中使用shiro的注解,但必须在配置了lifecycleBeanPostProcessor之后才能使用

6.配置shiroFilter;

  • id 必须和web.xml文件中配置的DelegatingFilterProxy的一致;若不一致,则会抛出:NOSuchBeanDefinitionException.因为Shiro回来IOC容器中查找名字对应的filter bean
  • 过滤器格式:url=拦截器[参数]
  • URL匹配顺序:URL 权限采取第一次匹配优先的方式,即从头开始 使用第一个匹配的 url 模式对应的拦截器链。? 如:

– /bb/**=filter1

– /bb/aa=filter2

– /**=filter3

– 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹 配,那么将使用 filter1 进行拦截。

  • url 模式使用 Ant 风格模式 ? Ant 路径通配符支持 ?、 * 、 **,注意通配符匹配不 包括目录分隔符“/”:

– ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不 匹配 /admin 或 /admin/;

– *:匹配零个或多个字符串,如 /admin* 将匹配 /admin、 /admin123,但不匹配 /admin/1;

– **:匹配路径中的零个或多个路径,如 /admin/** 将匹 配 /admin/a 或 /admin/a/b

  • 登入地址/认证失败地址:loginUrl
  • 认证和授权成功地址:successUrl
  • 认证成功授权失败地址:unauthorizedUrl
  •  过滤器:
过滤器 说明
authc 需要认证登录才能访问
user 用户拦截器,表示必须存在用户。
anon 匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
roles

角色授权拦截器,验证用户是或否拥有角色。

参数可写多个,表示某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个
参数都通过才算通过

authcBasic httpBasic 身份验证拦截器。
logout 退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
port 端口拦截器, 可通过的端口。
ssl ssl拦截器,只有请求协议是https才能通过

 拦截器可以静态配置(在shiro.xml中写死过滤器),也可以动态配置(可从数据库读取);选一种即可;

loginUrl&successUrl&unauthorizedUrl 地址对应的Controller(HomeController.java)

 1 package ssm.controller;
 2 
 3 import org.apache.shiro.SecurityUtils;
 4 import org.apache.shiro.authc.AuthenticationException;
 5 import org.apache.shiro.authc.IncorrectCredentialsException;
 6 import org.apache.shiro.authc.LockedAccountException;
 7 import org.apache.shiro.authc.UnknownAccountException;
 8 import org.apache.shiro.authc.UsernamePasswordToken;
 9 import org.apache.shiro.subject.Subject;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12 import org.springframework.stereotype.Controller;
13 import org.springframework.web.bind.annotation.RequestMapping;
14 import org.springframework.web.bind.annotation.RequestMethod;
15 import org.springframework.web.bind.annotation.RequestParam;
16 import org.springframework.web.servlet.ModelAndView;
17 
18 @Controller
19 public class HomeController {
20     private static final transient Logger log = LoggerFactory.getLogger(HomeController.class);
21 
22     @RequestMapping(value = "/login", method = RequestMethod.GET)
23     public ModelAndView login() {
24         ModelAndView mv = new ModelAndView("login");
25         return mv;
26     }
27 
28     @RequestMapping(value = "/role/admin", method = RequestMethod.GET)
29     public ModelAndView admin() {
30         ModelAndView mv = new ModelAndView("admin");
31         return mv;
32     }
33 
34     @RequestMapping(value = "/role/user", method = RequestMethod.GET)
35     public ModelAndView user() {
36         ModelAndView mv = new ModelAndView("user");
37         return mv;
38     }
39 
40     @RequestMapping(value = "/error/403", method = RequestMethod.GET)
41     public ModelAndView error() {
42         ModelAndView mv = new ModelAndView("unauthorized");
43         return mv;
44     }
45 
46     @RequestMapping(value = "/login", method = RequestMethod.POST)
47     public String login(@RequestParam(value = "username") String useranme,
48             @RequestParam(value = "password") String password) {
49         Subject currentUser = SecurityUtils.getSubject();
50         if (!currentUser.isAuthenticated()) {
51             // 用户名和密码封装为UsernamePasswordToken对象
52             UsernamePasswordToken token = new UsernamePasswordToken(useranme, password);
53             // 设置记住我
54             token.setRememberMe(true);
55             try {
56                 System.out.println(token.hashCode());
57                 // 执行登入
58                 currentUser.login(token);
59                 // 若没有指定的账户,则shiro将抛出UnknownAccountException
60             } catch (UnknownAccountException uae) {
61                 log.info("There is no user with username of " + token.getPrincipal());
62                 // 若账户存在,但密码不匹配,则shiro将抛出IncorrectCredentialsException
63             } catch (IncorrectCredentialsException ice) {
64                 log.info("Password for account " + token.getPrincipal() + " was incorrect!");
65                 // 若用户被锁定,抛出LockedAccountException
66             } catch (LockedAccountException lae) {
67                 log.info("The account for username " + token.getPrincipal() + " is locked.  "
68                         + "Please contact your administrator to unlock it.");
69             }
70             // 所有上面认证异常的父类
71             // ... catch more exceptions here (maybe custom ones specific to your
72             // application?
73             catch (AuthenticationException ae) {
74                 log.info("登入失败:" + ae.getMessage());
75             }
76         }
77         return "redirect:emp/list";
78     }
79 
80 }

shiro.xml配置文件(过滤器 静态配置):

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4     xsi:schemaLocation="
  5        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6 
  7 
  8     
 10     
 12     
 13     <bean id="securityManager"
 14         class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
 15         <property name="cacheManager" ref="cacheManager" />
 16         
 17         <property name="realm" ref="jdbcRealm" /> 
 18     bean>
 19     
 22     
 23     <bean id="cacheManager"
 24         class="org.apache.shiro.cache.ehcache.EhCacheManager">
 25         
 26         <property name="cacheManagerConfigFile"
 27             value="classpath:ehcache.xml" />
 28     bean>
 29 
 30     
 33     
 34     <bean id="jdbcRealm" class="ssm.shiro.ShiroRealm">
 35         <property name="credentialsMatcher">
 36             <bean
 37                 class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
 38                 
 39                 <property name="hashAlgorithmName" value="MD5">property>
 40                 
 41                 <property name="hashIterations" value="1024">property>
 42                 <property name="storedCredentialsHexEncoded" value="true"/>
 43             bean>
 44         property>
 45     bean>
 46 
 47     
 49     
 53     
 55     <bean id="lifecycleBeanPostProcessor"
 56         class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
 57 
 58     
 60     
 61     <bean
 62         class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
 63         depends-on="lifecycleBeanPostProcessor" />
 64     <bean
 65         class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 66         <property name="securityManager" ref="securityManager" />
 67     bean>
 68 
 69 
 70 
 71     
 76     
 84     <bean id="shiroFilter"
 85         class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 86         <property name="securityManager" ref="securityManager" />
 87         <property name="loginUrl" value="/login" />
 88         <property name="successUrl" value="/emp/list" />
 89         <property name="unauthorizedUrl" value="/error/403" />
 90         
 95          <property name="filterChainDefinitions">
 96             <value>
 97                 # allow WebStart to pull the jars for the swing app: 
 98                 /*.jar = anon
 99                 /login = anon
100                 /logout = logout
101                  /role/user=roles[user]
102                 /role/admin=roles[admin]
103                 
104                 # everything else requires authentication:
105                 /** = authc
106             value>
107         property> 
108     bean>
109 beans>

拦截器的动态配置:

 1 <bean id="shiroFilter"
 2         class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 3         <property name="securityManager" ref="securityManager" />
 4         <property name="loginUrl" value="/login" />
 5         <property name="successUrl" value="/emp/list" />
 6         <property name="unauthorizedUrl" value="/error/403" />
 7         
12         <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap">property>
13     bean>
14     
15     
16     <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="bulidFilterChainDefinitionMap">bean>
17     <bean id="filterChainDefinitionMapBuilder" class="ssm.shiro.factory.FilterChainDefinitionMapBuilder">bean>

FilterChainDefinitionMapBuilder:

package ssm.shiro.factory;

import java.util.LinkedHashMap;

public class FilterChainDefinitionMapBuilder {

    public LinkedHashMap bulidFilterChainDefinitionMap() {
        LinkedHashMap map = new LinkedHashMap<>();
        // /login = anon
        // /logout = logout
        // /role/user=roles[user]
        // /role/admin=roles[admin]
        // # everything else requires authentication:
        // /** = authc
        map.put("/login", "anon");
        map.put("/logout", "logout");
        map.put("/js/**", "anon");
        map.put("/role/user", "roles[user]");
        map.put("/role/admin", "roles[admin]");
        map.put("/emp/list", "user");
        map.put("/**", "authc");
        return map;
    }
}

shiro.xml配置文件(过滤器 动态配置):

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4     xsi:schemaLocation="
  5        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6 
  7 
  8     
 10     
 12     
 13     <bean id="securityManager"
 14         class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
 15         <property name="cacheManager" ref="cacheManager" />
 16         
 17         <property name="realm" ref="jdbcRealm" /> 
 18     bean>
 19     
 22     
 23     <bean id="cacheManager"
 24         class="org.apache.shiro.cache.ehcache.EhCacheManager">
 25         
 26         <property name="cacheManagerConfigFile"
 27             value="classpath:ehcache.xml" />
 28     bean>
 29 
 30     
 33     
 34     <bean id="jdbcRealm" class="ssm.shiro.ShiroRealm">
 35         <property name="credentialsMatcher">
 36             <bean
 37                 class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
 38                 
 39                 <property name="hashAlgorithmName" value="MD5">property>
 40                 
 41                 <property name="hashIterations" value="1024">property>
 42                 <property name="storedCredentialsHexEncoded" value="true"/>
 43             bean>
 44         property>
 45     bean>
 46 
 47     
 49     
 53     
 55     <bean id="lifecycleBeanPostProcessor"
 56         class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
 57 
 58     
 60     
 61     <bean
 62         class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
 63         depends-on="lifecycleBeanPostProcessor" />
 64     <bean
 65         class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 66         <property name="securityManager" ref="securityManager" />
 67     bean>
 68 
 69 
 70 
 71     
 76     
 84     <bean id="shiroFilter"
 85         class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 86         <property name="securityManager" ref="securityManager" />
 87         <property name="loginUrl" value="/login" />
 88         <property name="successUrl" value="/emp/list" />
 89         <property name="unauthorizedUrl" value="/error/403" />
 90         
 95         <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap">property>
 96     bean>
 97     
 98     
 99     <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="bulidFilterChainDefinitionMap">bean>
100     <bean id="filterChainDefinitionMapBuilder" class="ssm.shiro.factory.FilterChainDefinitionMapBuilder">bean>
101 beans>

web.xml配置:

参考地址 /shiro-root-1.3.2/samples/web/src/main/webapp/WEB-INF/web.xml;

需要修改两个地方:1.添加shiro filter 2.读取配置文件需要添加shiro.xml 和springmvc.xml(由于Realm中需要使用自动装配@Autowired 所以需要在这里加入,否则就报无法找到 自动装配的bean):

a.需要添加shiroFilter

 1  <filter>
 2         <filter-name>shiroFilterfilter-name>
 3         <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
 4         <init-param>
 5             <param-name>targetFilterLifecycleparam-name>
 6             <param-value>trueparam-value>
 7         init-param>
 8     filter>
 9 
10     <filter-mapping>
11         <filter-name>shiroFilterfilter-name>
12         <url-pattern>/*url-pattern>
13     filter-mapping>

b.读取配置文件额外添加shiro.xml 和 springmvc配置文件

1 
2         contextConfigLocation
3         classpath:springmvc.xml,classpath:spring.xml,classpath:shiro.xml
4     

web.xml完整文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3     xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 4     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
 5     version="3.1">
 6     <context-param>
 7         <param-name>contextConfigLocationparam-name>
 8         <param-value>classpath:springmvc.xml,classpath:spring.xml,classpath:shiro.xmlparam-value>
 9     context-param>
10     <filter>
11         <filter-name>CharacterEncodingFilterfilter-name>
12         <filter-class>org.springframework.web.filter.CharacterEncodingFilter
13         filter-class>
14         <init-param>
15             <param-name>encodingparam-name>
16             <param-value>utf-8param-value>
17         init-param>
18     filter>
19     <filter-mapping>
20         <filter-name>CharacterEncodingFilterfilter-name>
21         <url-pattern>/*url-pattern>
22     filter-mapping>
23     <listener>
24         <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
25     listener>
26     
27     <servlet>
28         <servlet-name>springDispatcherServletservlet-name>
29         <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
30         
31         
33         <init-param>
34             <param-name>contextConfigLocationparam-name>
35             <param-value>classpath:springmvc.xmlparam-value>
36         init-param>
37         
38         <load-on-startup>1load-on-startup>
39     servlet>
40 
41     
42     <servlet-mapping>
43         <servlet-name>springDispatcherServletservlet-name>
44         <url-pattern>/url-pattern>
45     servlet-mapping>
46     
47     
48      <filter>
49         <filter-name>shiroFilterfilter-name>
50         <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
51         <init-param>
52             <param-name>targetFilterLifecycleparam-name>
53             <param-value>trueparam-value>
54         init-param>
55     filter>
56 
57     <filter-mapping>
58         <filter-name>shiroFilterfilter-name>
59         <url-pattern>/*url-pattern>
60     filter-mapping>
61 web-app>

 三、身份认证:

认证步骤:

1.获取当前的Subject。调用 SecurityUtils.getSubject();
2.测试当前用户是否已经被认证。即是否已经登入。调用Subject 的 isAuthenticated();
3.若没有被认证,则把用户名和密码封装为UsernamePasswordToken对象,表单页面用来获取用户名密码;

1). 创建一个表单页面;
2).把请求提交到springMVC的handler
3).获取用户名和密码

4.执行登入:调用Subject 的 login(AuthenticationToken)方法;
5.自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro。

1).实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm类
2).实现doGetAuthenticationInfo(AuthenticationToken)方法;

6.由shiro完成对密码的比对:密码的比对:通过AuthenticatingRealm的credentialsMatcher属性来进行的密码比对

获取当前的Subject&当前用户是否已经被认证:

执行登入currentUser.login(token)时,实际上调用的是:Realm中的doGetAuthenticationInfo方法;

 1 @RequestMapping(value = "/login", method = RequestMethod.POST)
 2     public String login(@RequestParam(value = "username") String useranme,
 3             @RequestParam(value = "password") String password) {
 4         Subject currentUser = SecurityUtils.getSubject();
 5         if (!currentUser.isAuthenticated()) {
 6             // 用户名和密码封装为UsernamePasswordToken对象
 7             UsernamePasswordToken token = new UsernamePasswordToken(useranme, password);
 8             // 设置记住我
 9             token.setRememberMe(true);
10             try {
11                 System.out.println(token.hashCode());
12                 // 执行登入
13                 currentUser.login(token);
14                 // 若没有指定的账户,则shiro将抛出UnknownAccountException
15             } catch (UnknownAccountException uae) {
16                 log.info("There is no user with username of " + token.getPrincipal());
17                 // 若账户存在,但密码不匹配,则shiro将抛出IncorrectCredentialsException
18             } catch (IncorrectCredentialsException ice) {
19                 log.info("Password for account " + token.getPrincipal() + " was incorrect!");
20                 // 若用户被锁定,抛出LockedAccountException
21             } catch (LockedAccountException lae) {
22                 log.info("The account for username " + token.getPrincipal() + " is locked.  "
23                         + "Please contact your administrator to unlock it.");
24             }
25             // 所有上面认证异常的父类
26             // ... catch more exceptions here (maybe custom ones specific to your
27             // application?
28             catch (AuthenticationException ae) {
29                 log.info("登入失败:" + ae.getMessage());
30             }
31         }
32         return "redirect:emp/list";
33     }

自定义Realm实现(AuthenticatingRealm方式):

实现方式:AuthenticatingRealm 和AuthorizingRealm方式;一般使用AuthorizingRealm方式,因为除了带认证,还有授权;区别就是:AuthenticatingRealm 只有认证,而 AuthorizingRealm 有认证、有授权;

  • principals:身份,即主体的标识属性,可以是任何属性,如用户名、 邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/邮箱/手机号。
  • credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证 书等。 
  • 最常见的 principals 和 credentials 组合就是用户名/密码了
 1 public class ShiroRealm extends AuthenticatingRealm {
 2 
 3     @Autowired
 4     private UserService userService;
 5 
 6     @Override
 7     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
 8         System.out.println("doGetAuthenticationInfo:" + token.hashCode());
 9         // 1.把AuthenticationToken 强转为usernamePasswordToken
10         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
11         // 2.usernamePasswordTokenh获取username
12         String username = usernamePasswordToken.getUsername();
13         // 3.调用数据库方法,从数据库中查询username对应的用户记录
14         System.out.println("从数据库获取username:" + username + ",所对应的记录");
15         User user = userService.getByUser(username);
16         // 4.若用户不存在,则可以抛出UnknownAccountException异常;
17         if (user != null) {
18             if ("unknown".equals(username)) {
19                 throw new UnknownAccountException();
20             }
21             // 5.根据用户信息的情况,决定是否需要抛出其他的AuthenticcationException异常
22             if ("monster".equals(username)) {
23                 throw new LockedAccountException();
24             }
25             // 6.根据用户的情况,来构建AuthenticationInfo
26             // 以下信息时从数据库中获取的
27             // 1).principal:实体的认证信息,可以是username,也可以是数据表对应的用户的实体类对象
28             Object principal = user;
29             // 2).credentials:密码(后台查询得到的密码)
30             Object credentials = user.getPassword();
31             // 3).realmName:当前realm对象的name,调用父类getName()方法即可;
32             String realmName = getName();
33             // 4).盐值
34             ByteSource salt = ByteSource.Util.bytes(username);
35             // SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
36             SimpleAuthenticationInfo info2 = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
37             return info2;
38         } else {
39             throw new UserException();
40         }
41 
42     }
43 
44 }

 运行代码,输入正确的用户名密码,就可以登入到配置的首页了即:emp/list;如果直接用AuthorizingRealm 方式:即如下方式;

 1 package ssm.shiro;
 2 
 3 import org.apache.shiro.authc.AuthenticationException;
 4 import org.apache.shiro.authc.AuthenticationInfo;
 5 import org.apache.shiro.authc.AuthenticationToken;
 6 import org.apache.shiro.authc.LockedAccountException;
 7 import org.apache.shiro.authc.SimpleAuthenticationInfo;
 8 import org.apache.shiro.authc.UnknownAccountException;
 9 import org.apache.shiro.authc.UsernamePasswordToken;
10 import org.apache.shiro.authz.AuthorizationInfo;
11 import org.apache.shiro.crypto.hash.SimpleHash;
12 import org.apache.shiro.realm.AuthorizingRealm;
13 import org.apache.shiro.subject.PrincipalCollection;
14 import org.apache.shiro.util.ByteSource;
15 import org.springframework.beans.factory.annotation.Autowired;
16 
17 import ssm.entity.User;
18 import ssm.service.UserService;
19 import ssm.utils.UserException;
20 
21 public class ShiroRealm extends AuthorizingRealm {
22 
23     @Autowired
24     private UserService userService;
25 
26     @Override
27     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
28         System.out.println("doGetAuthenticationInfo:" + token.hashCode());
29         // 1.把AuthenticationToken 强转为usernamePasswordToken
30         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
31         // 2.usernamePasswordTokenh获取username
32         String username = usernamePasswordToken.getUsername();
33         // 3.调用数据库方法,从数据库中查询username对应的用户记录
34         System.out.println("从数据库获取username:" + username + ",所对应的记录");
35         User user = userService.getByUser(username);
36         // 4.若用户不存在,则可以抛出UnknownAccountException异常;
37         if (user != null) {
38             if ("unknown".equals(username)) {
39                 throw new UnknownAccountException();
40             }
41             // 5.根据用户信息的情况,决定是否需要抛出其他的AuthenticcationException异常
42             if ("monster".equals(username)) {
43                 throw new LockedAccountException();
44             }
45             // 6.根据用户的情况,来构建AuthenticationInfo
46             // 以下信息时从数据库中获取的
47             // 1).principal:实体的认证信息,可以是username,也可以是数据表对应的用户的实体类对象
48             Object principal = user;
49             // 2)。credentials:密码(后台查询得到的密码)
50             Object credentials = user.getPassword();
51             // 3)。realmName:当前realm对象的name,调用父类getName()方法即可;
52             String realmName = getName();
53             // 4).盐值
54             ByteSource salt = ByteSource.Util.bytes(username);
55             // SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
56             SimpleAuthenticationInfo info2 = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
57             return info2;
58         } else {
59             throw new UserException();
60         }
61 
62     }
63 
64     // 授权会被shiro回调的方法
65     @Override
66     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
67         return null;
68     }
69 }

加密

盐值加密

盐值加密的作用:防止 如果用户名不同,但是密码相同时,MD5加密后 ,密码相同;

步骤:

1)。doGetAuthenticationInfo的方法返回值创建SimpleAuthenticationInfo对象时,通过 SimpleAuthenticationInfo(principal, credentials, salt, realmName)
构造器创建;
2)。使用ByteSource.Util.bytes 来计算盐值
3)。盐值需要唯一:一般使用userid;
4).使用impleHash(hashAlgorithmName, credentials, salt, hashIterations)来计算盐值加密后的密码值;

前端页面密码加密配置(shiro.xml中的配置)

 1 <bean id="jdbcRealm" class="ssm.shiro.ShiroRealm">
 2         <property name="credentialsMatcher">
 3             <bean
 4                 class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
 5                 
 6                 <property name="hashAlgorithmName" value="MD5">property>
 7                 
 8                 <property name="hashIterations" value="1024">property>
 9                 <property name="storedCredentialsHexEncoded" value="true"/>
10             bean>
11         property>
12     bean>

md5 盐值加密:

1 public static void main(String[] args) {
2         String hashAlgorithmName = "MD5";
3         Object credentials = "123456";
4         Object salt = null;
5         int hashIterations = 1024;
6         SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
7         System.out.println(simpleHash);
8     }

四、授权

Shiro 支持三种方式的授权:

  • 编程式:通过写if/else 授权代码块完成
  • 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相 应的异常
  • JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成 

Shiro 提供了 JSTL 标签

用于在 JSP 页面进行权限控制,如 根据登录用户显示相应的页面按钮。 ? guest 标签:用户没有身份验证时显示相应信息,即游客 访问信息:

实现方式:

授权java代码:

 1     // 授权会被shiro回调的方法
 2     @Override
 3     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
 4         // 1.从PrincipalCollection中获取登入用户信息
 5         User user = (User) principalCollection.getPrimaryPrincipal();
 6         // 2.利用登入的用户信息来获取当前的角色或权限(可能需要查询数据库)
 7         Set roles = new HashSet<>();
 8         roles.add("user");
 9         if ("Y".equals(user.getIsadmin())) {
10             roles.add("admin");
11         }
12         // 3.创建SimpleAuthorizationInfo,并设置其roles属性
13         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
14         // 4.返回SimpleAuthorizationInfo对象
15         return simpleAuthorizationInfo;
16     }

index.jsp页面代码:

 1 <p>
 2 Welcom <shiro:principal>shiro:principal>
 3 p>
 4 
 5 <input type="button" value="添加" id="add">
 6 <a href="/logout"><s:message code="SYS.LOGOUT">s:message>a>
 7    
 8 <shiro:hasRole name="admin">
 9 <a href="/role/admin"><s:message code="SYS.ROLE.ADMIN">s:message>a>
10    
11 shiro:hasRole>
12 <shiro:hasRole name="user">
13 <a href="/role/user"><s:message code="SYS.ROLE.USER">s:message>a>
14    
15 shiro:hasRole>

完整的index.jsp代码:

  1 <%@ page language="java" contentType="text/html; charset=UTF-8"
  2     pageEncoding="UTF-8"%>
  3 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
  4 <%@taglib prefix="s" uri="http://www.springframework.org/tags"%>
  5 <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
  6 DOCTYPE html>
  7 <html>
  8 <head>
  9 <script type="text/javascript" src="/js/jquery.min.js">script>
 10 <meta charset="UTF-8">
 11 <title><s:message code="EMPLOYEE.LIST">s:message>title>
 12 head>
 13 <body>
 14 <p>
 15 Welcom <shiro:principal>shiro:principal>
 16 p>
 17 
 18 <input type="button" value="添加" id="add">
 19 <a href="/logout"><s:message code="SYS.LOGOUT">s:message>a>
 20    
 21 <shiro:hasRole name="admin">
 22 <a href="/role/admin"><s:message code="SYS.ROLE.ADMIN">s:message>a>
 23    
 24 shiro:hasRole>
 25 <shiro:hasRole name="user">
 26 <a href="/role/user"><s:message code="SYS.ROLE.USER">s:message>a>
 27    
 28 shiro:hasRole>
 29 
 30 <a href="/emp/testAuth"><s:message code="SYS.ROLE.TESTAUTH">s:message>a>
 31     <table>
 32         <tr>
 33             <td>idtd>
 34             <td><s:message code="EMPLOYEE.LASTNAME">s:message> td>
 35             <td><s:message code="EMPLOYEE.EMAIL">s:message>td>
 36             <td><s:message code="EMPLOYEE.GENDER">s:message>td>
 37             <td><s:message code="SYS.OPERATE">s:message>td>
 38         tr>
 39         <c:forEach var="item" items="${pages.list}">
 40             <tr class="row">
 41                 <td>${item.id}td>
 42                 <td>${item.lastName}td>
 43                 <td>${item.email}td>
 44                 <td>${item.gender}td>
 45                 <td > <input type="button" value=" "  class="editRow">
 46                 <input type="hidden" value="${item.id}"/>
 47                 td>
 48                 <td > <input type="button" value=" "  class="detailRow">
 49                 <input type="hidden" value="${item.id}"/>
 50                 td>
 51                 
 52             tr>
 53         c:forEach>
 54     table>
 55     <p>
 56         <c:if test="${pages.hasPreviousPage}">
 57             <input type="button" class="page" value="前一页"/>
 58             <input type="hidden" value="${pages.prePage}">
 59         c:if>
 60         <c:forEach var="item" items="${pages.navigatepageNums}">
 61             <c:if test="${item==pages.pageNum}">
 62                 <input type="button" class="page" style="color: red" value="${item}">
 63             c:if>
 64             <c:if test="${item!=pages.pageNum}">
 65                 <input type="button" class="page" value="${item}">
 66             c:if>
 67             <input type="hidden" value="${item}">
 68         c:forEach>
 69         <c:if test="${pages.hasNextPage}">
 70             <input type="button" class="page" value="下一页"/>
 71             <input type="hidden" value="${pages.nextPage}">
 72         c:if>
 73         <input type="hidden" id="pageNum" value="${pages.pageNum}"> <input
 74             type="hidden" id="pageSize" value="${pages.pageSize}">
 75     p>
 76 body>
 77 <script>
 78     $(function() {
 79         $(".page").each(function() {
 80                             $(this).click(function() {
 81                                                 var pageNum = $(this).next().val();
 82                                                 var pageSize = 5;
 83                                                 window.location = "${pageContext.request.contextPath}/emp/list?pageNum="
 84                                                         + pageNum
 85                                                         + "&pageSize="
 86                                                         + pageSize;
 87                                             })
 88                         });
 89         $("#add").click(function(){
 90             window.location = "${pageContext.request.contextPath}/emp/add"
 91         })
 92         $(".editRow").each(function(){
 93             $(this).click(function(){
 94                 var id = $(this).next().val();
 95                 window.location = "edit/"+id
 96             })
 97         })
 98         $(".detailRow").each(function(){
 99             $(this).click(function(){
100                 var id = $(this).next().val();
101                 $.post("/emp/detail",{id:id},function(data){
102                     alert("姓名:"+data.lastName+",邮箱:"+data.email+",性别:"+data.gender);
103                 })
104                 
105             })
106         })
107         
108     })
109 script>
110 html>

权限注解:

在EmployeeController中添加

1     @RequiresRoles({ "admin" })
2     @RequestMapping("/testAuth")
3     public String testAuth(HttpSession session) {
4         session.setAttribute("key", "value123");
5         employeeService.testAuth();
6         return "redirect:/emp/list";
7     }

在service中获取session

1 @Override
2     public void testAuth() {
3         Session session = SecurityUtils.getSubject().getSession();
4         String value = (String) session.getAttribute("key");// service 层也可以访问到session
5         System.out.println(new Date());
6         System.out.println("-------------->session value:" + value);
7     }

在页面点击 测试权限注解,打印-------------->session value:value123;

五、记住我的配置 rememberme

1、首先在登录页面选中 RememberMe 然后登录成功;如果是 浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并 保存下来;

2、关闭浏览器再重新打开;会发现浏览器还是记住你的;

3、访问一般的网页服务器端还是知道你是谁,且能正常访问;

认证和记住我:

subject.isAuthenticated() 表示用户进行了身份验证登录的, 即使有 Subject.login 进行了登录;

subject.isRemembered():表示用户是通过记住我登录的, 此时可能并不是真正的你(如你的朋友使用你的电脑,或者 你的cookie 被窃取)在访问的 

两者二选一,即 subject.isAuthenticated()==true,则 subject.isRemembered()==false;反之一样

记住我的配置

1 
2     <bean id="securityManager"
3         class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
4         <property name="cacheManager" ref="cacheManager" />
5         
6         <property name="realm" ref="jdbcRealm" /> 
7        <property name="rememberMeManager.cookie.maxAge" value="10">property>
8     bean>

 login.jsp 添加了 记住我选项:

 1 <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8"%>
 3 DOCTYPE html>
 4 <html>
 5 <head>
 6 <meta charset="UTF-8">
 7 <title>登入title>
 8 <script type="text/javascript" src="/js/jquery.min.js">script>
 9 head>
10 <body>
11 <h4>Login pageh4>
12 
13 <form action="/login" method="post">
14 用户名:<input type="text" name="username" id="username"><br>
15   码:<input type="password" name="password" id="password"><br>
16 记住我:<input type="checkbox"  id="rememberme">
17 <input type="hidden" name="rememberme" value="off"><br>
18 <input type="submit" value="提交">
19 
20 form>
21 body>
22 <script>
23 $(function(){
24     $("#rememberme").on("change",function(){
25        var checkValue =  $(this).val();
26         $(this).next().val(checkValue);
27     })
28 })
29 script>
30 html>

 记住我 HomeControll  POST 的login  修改:

 1  @RequestMapping(value = "/login", method = RequestMethod.POST)
 2     public String login(@RequestParam(value = "username") String useranme,
 3             @RequestParam(value = "password") String password, @RequestParam(value = "rememberme") String rememberme) {
 4         Subject currentUser = SecurityUtils.getSubject();
 5         if (!currentUser.isAuthenticated()) {
 6             // 用户名和密码封装为UsernamePasswordToken对象
 7             UsernamePasswordToken token = new UsernamePasswordToken(useranme, password);
 8             // 设置记住我
 9             if (rememberme.equals("on")) {
10                 token.setRememberMe(true);
11             }
12             try {
13                 System.out.println(token.hashCode());
14                 // 执行登入
15                 currentUser.login(token);
16                 // 若没有指定的账户,则shiro将抛出UnknownAccountException
17             } catch (UnknownAccountException uae) {
18                 log.info("There is no user with username of " + token.getPrincipal());
19                 // 若账户存在,但密码不匹配,则shiro将抛出IncorrectCredentialsException
20             } catch (IncorrectCredentialsException ice) {
21                 log.info("Password for account " + token.getPrincipal() + " was incorrect!");
22                 // 若用户被锁定,抛出LockedAccountException
23             } catch (LockedAccountException lae) {
24                 log.info("The account for username " + token.getPrincipal() + " is locked.  "
25                         + "Please contact your administrator to unlock it.");
26             }
27             // 所有上面认证异常的父类
28             // ... catch more exceptions here (maybe custom ones specific to your
29             // application?
30             catch (AuthenticationException ae) {
31                 log.info("登入失败:" + ae.getMessage());
32             }
33         }
34         return "redirect:emp/list";
35     }

六、多Realm配置

实列:secondRealm.java,加密方式SHA1;

 1 package ssm.shiro;
 2 
 3 import org.apache.shiro.authc.AuthenticationException;
 4 import org.apache.shiro.authc.AuthenticationInfo;
 5 import org.apache.shiro.authc.AuthenticationToken;
 6 import org.apache.shiro.authc.LockedAccountException;
 7 import org.apache.shiro.authc.SimpleAuthenticationInfo;
 8 import org.apache.shiro.authc.UnknownAccountException;
 9 import org.apache.shiro.authc.UsernamePasswordToken;
10 import org.apache.shiro.authz.AuthorizationInfo;
11 import org.apache.shiro.crypto.hash.SimpleHash;
12 import org.apache.shiro.realm.AuthorizingRealm;
13 import org.apache.shiro.subject.PrincipalCollection;
14 import org.apache.shiro.util.ByteSource;
15 import org.springframework.beans.factory.annotation.Autowired;
16 
17 import ssm.entity.User;
18 import ssm.service.UserService;
19 
20 public class SecondShiroRealm extends AuthorizingRealm {
21     @Autowired
22     private UserService userService;
23 
24     @Override
25     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
26         System.out.println("SecondShiroRealm-doGetAuthenticationInfo:" + token.hashCode());
27         // 1.把AuthenticationToken 强转为usernamePasswordToken
28         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
29         // 2.usernamePasswordTokenh获取username
30         String username = usernamePasswordToken.getUsername();
31         // 3.调用数据库方法,从数据库中查询username对应的用户记录
32         System.out.println("从数据库获取username:" + username + ",所对应的记录");
33         User user = userService.getByUser(username);
34         // 4.若用户不存在,则可以抛出UnknownAccountException异常;
35         if (user != null) {
36 
37             // 5.根据用户信息的情况,决定是否需要抛出其他的AuthenticcationException异常
38             if ("monster".equals(username)) {
39                 throw new LockedAccountException();
40             }
41             // 6.根据用户的情况,来构建AuthenticationInfo
42             // 以下信息时从数据库中获取的
43             // 1).principal:实体的认证信息,可以是username,也可以是数据表对应的用户的实体类对象
44             Object principal = user;
45             // 2)。credentials:密码(后台查询得到的密码)
46             Object credentials = user.getPassword();
47             // 3)。realmName:当前realm对象的name,调用父类getName()方法即可;
48             String realmName = getName();
49             // 4)。设置盐值
50             ByteSource salt = ByteSource.Util.bytes(username);
51             // SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
52             SimpleAuthenticationInfo info2 = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
53             return info2;
54         } else {
55             throw new UnknownAccountException();
56         }
57 
58     }
59 
60     public static void main(String[] args) {
61         String hashAlgorithmName = "SHA1";
62         Object credentials = "123456";
63         Object salt = ByteSource.Util.bytes("user");
64         int hashIterations = 1024;
65         SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
66         System.out.println(simpleHash);
67     }
68 
69     @Override
70     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
71         return null;
72     }
73 
74 }

配置信息:

多Realm 需要配置认证策略:

  • FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第 一个 Realm 身份验证成功的认证信息,其他的忽略;
  • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信 息;
  • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息,如果有一个失败就失败了。
  • ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略
 1 <bean id="securityManager"
 2         class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
 3         <property name="cacheManager" ref="cacheManager" />
 4         
 5         <property name="authenticator" ref="authenticator">property>
 6         <property name="realms">
 7             <list>
 8                 <ref bean="jdbcRealm">ref>
 9                 <ref bean="secondShiroRealm">ref>
10             list>
11         property>
12        <property name="rememberMeManager.cookie.maxAge" value="10000">property>
13     bean>
14     
15     <bean id="authenticator"
16         class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
17         
18         
25         <property name="authenticationStrategy">
26             <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy">bean>
27         property>
28     bean>
29     
30     <bean id="secondShiroRealm" class="ssm.shiro.SecondShiroRealm">
31         <property name="credentialsMatcher">
32             <bean
33                 class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
34                 
35                 <property name="hashAlgorithmName" value="SHA1">property>
36                 
37                 <property name="hashIterations" value="1024">property>
38                 <property name="storedCredentialsHexEncoded" value="true"/>
39             bean>
40         property>
41     bean>

 当登入 用户名为 user , 密码为 123456 时 也会登入成功;

附录:

表结构&数据:

tbl_user:

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80025
 Source Host           : localhost:3306
 Source Schema         : mybatis

 Target Server Type    : MySQL
 Target Server Version : 80025
 File Encoding         : 65001

 Date: 13/05/2022 11:11:50
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tbl_user
-- ----------------------------
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user`  (
  `ID` int NOT NULL AUTO_INCREMENT,
  `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `PASSWORD` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `ISADMIN` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_user
-- ----------------------------
INSERT INTO `tbl_user` VALUES (1, 'joy', '02b7e99ffcb1145409f1b72da04b9f5f', 'Y');
INSERT INTO `tbl_user` VALUES (2, 'tom', '93c3ef4bc1b01b865028192eeda1a41d', 'N');
INSERT INTO `tbl_user` VALUES (3, 'user', '073d4c3ae812935f23cb3f2a71943f49e082a718', 'N');

SET FOREIGN_KEY_CHECKS = 1;

tbl_employee

 1 /*
 2  Navicat Premium Data Transfer
 3 
 4  Source Server         : localhost
 5  Source Server Type    : MySQL
 6  Source Server Version : 80025
 7  Source Host           : localhost:3306
 8  Source Schema         : mybatis
 9 
10  Target Server Type    : MySQL
11  Target Server Version : 80025
12  File Encoding         : 65001
13 
14  Date: 13/05/2022 11:11:33
15 */
16 
17 SET NAMES utf8mb4;
18 SET FOREIGN_KEY_CHECKS = 0;
19 
20 -- ----------------------------
21 -- Table structure for tbl_employee
22 -- ----------------------------
23 DROP TABLE IF EXISTS `tbl_employee`;
24 CREATE TABLE `tbl_employee`  (
25   `id` int NOT NULL AUTO_INCREMENT,
26   `d_id` int NULL DEFAULT NULL,
27   `last_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
28   `gender` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
29   `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
30   PRIMARY KEY (`id`) USING BTREE
31 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
32 
33 -- ----------------------------
34 -- Records of tbl_employee
35 -- ----------------------------
36 INSERT INTO `tbl_employee` VALUES (1, 1, 'JoyLi', '', '1602211058@qq.com');
37 
38 SET FOREIGN_KEY_CHECKS = 1;