一、关于shiro
一个shiro的配置案例如下:
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
   | <?xml version="1.0" encoding="UTF-8"?> <beans>
           <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">         <property name="securityManager" ref="securityManager"/>         <property name="loginUrl" value="${zheng.upms.sso.server.url}"/>         <property name="successUrl" value="${zheng.upms.successUrl}"/>         <property name="unauthorizedUrl" value="${zheng.upms.unauthorizedUrl}"/>         <property name="filters">             <util:map>                                  <entry key="authc" value-ref="upmsAuthenticationFilter"/>             </util:map>         </property>         <property name="filterChainDefinitions">             <value>                                  /manage/** = upmsSessionForceLogout,authc                 /manage/index = user                 /druid/** = user                 /swagger-ui.html = user                 /resources/** = anon                 /** = anon             </value>         </property>     </bean>
           <bean id="upmsAuthenticationFilter" class="com.zheng.upms.client.shiro.filter.UpmsAuthenticationFilter"/>
           <bean id="upmsSessionForceLogout" class="com.zheng.upms.client.shiro.filter.UpmsSessionForceLogoutFilter"/>
           <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">         <property name="realms">             <list><ref bean="upmsRealm"/></list>         </property>         <property name="sessionManager" ref="sessionManager"/>         <property name="rememberMeManager" ref="rememberMeManager"/>     </bean>
           <bean id="upmsRealm" class="com.zheng.upms.client.shiro.realm.UpmsRealm"></bean>
           <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">                  <property name="globalSessionTimeout" value="${zheng.upms.session.timeout}"/>                  <property name="sessionDAO" ref="sessionDAO"/>         <property name="sessionIdCookieEnabled" value="true"/>         <property name="sessionIdCookie" ref="sessionIdCookie"/>         <property name="sessionValidationSchedulerEnabled" value="false"/>         <property name="sessionListeners">             <list><ref bean="sessionListener"/></list>         </property>         <property name="sessionFactory" ref="sessionFactory"/>     </bean>
           <bean id="sessionDAO" class="com.zheng.upms.client.shiro.session.UpmsSessionDao"/>
           <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">                  <property name="httpOnly" value="true"/>                  <property name="maxAge" value="-1"/>                  <property name="name" value="${zheng.upms.session.id}"/>     </bean>
           <bean id="sessionListener" class="com.zheng.upms.client.shiro.listener.UpmsSessionListener"/>
           <bean id="sessionFactory" class="com.zheng.upms.client.shiro.session.UpmsSessionFactory"/>
           <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">                  <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>         <property name="cookie" ref="rememberMeCookie"/>     </bean>
           <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">         <constructor-arg value="rememberMe"/>                  <property name="httpOnly" value="true"/>                  <property name="maxAge" value="${zheng.upms.rememberMe.timeout}"/>     </bean>
           <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">         <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>         <property name="arguments" ref="securityManager"/>     </bean>
           <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>     <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">         <property name="securityManager" ref="securityManager"/>     </bean>
           <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
  </beans>
   | 
 
可见, 三大模块:securityManager、Realm、subject
其中SecurityManager是链接的主体,讲Realm注入其中,并利用Subject subject = SecurityUtils.getSubject();可以获得当前的subject
二、subject.login()发生了什么
参考
所以最终还是回归到了我们自定义的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法。
三、权限验证
(1) 可以简单书写如下:
1 2 3 4 5 6 7 8 9
   | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {           UsernamePasswordToken token = (UsernamePasswordToken) authcToken;           User user = accountManager.findUserByUserName(token.getUsername());           if (user != null) {               return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());           } else {               return null;           }   } 
  | 
 
但是这时候我们就回有一疑问:这里面都没有验证密码?这个其实是交给credentialsMatcher去验证的。所以我们可以自定义一个bean,其实现类继承自SimpleCredentialsMatcher,复写doCredentialsMatch方法:
1 2 3 4 5 6 7 8 9
   | public class MyCredentialsMatcher extends SimpleCredentialsMatcher {     @Override     public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;         String password = String.valueOf(usernamePasswordToken.getPassword());         String dbpassword = (String) info.getCredentials();         return this.equals(password, dbpassword);     } }
  | 
 
参考一–参考二
最后MyCredentialsMatcher这个bean注入自定义Realm中。
(2) 也可以不用自定义MyCredentialsMatcher,直接在doGetAuthenticationInfo把密码校验给校验了。zheng项目就是这么做的。
四、realm和subject的个人理解
realm可以理解为安全数据源。
自定义的realm,如继承自AuthorizingRealm,实现了doGetAuthenticationInfo(AuthenticationToken authenticationToken)(这个方法之后用户就获得了用户是否验证通过信息保存到subject对象中)和doGetAuthorizationInfo(PrincipalCollection principalCollection)(这个方法之后,就可以检查用户是否具备了某些权限保存到了subject对象中)。在业务代码中我们可以这样判断authenticate和authorization结果:
1 2 3
   | Subject subject = SecurityUtils.getSubject(); subject.hasRole("admin");        subject.isAuthenticated();      
   | 
 
上面理解是错误的,实际上是:
Subject自己不会实现相应的身份验证/授权逻辑,而是通过DelegatingSubject委托给SecurityManager实现;及可以理解为Subject是一个面门。参考
subject的使用:
- 身份验证(login)
 
- 授权(hasRole*/isPermitted或checkRole/checkPermission*)
 
- 将相应的数据存储到会话(Session)
 
- 切换身份(RunAs)/多线程身份传播
 
- 退出
 
1、首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而
SecurityManager 接着会委托给 Authorizer;
2、Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过PermissionResolver 把字符串转换成相应的 Permission 实例;
3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的
角色/权限;
4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给
ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole*会返回 true,否则返回 false 表示授权失败
关于授权:
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware,  RolePermissionResolverAware可见AuthorizingRealm实现了Authorizer,所有就有了isPermitted/hasRole方法
五、SecurityManager和SecurityUtils设置
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
   | public void test() {
          DefaultSecurityManager securityManager = new DefaultSecurityManager();
                   ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();         authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());         securityManager.setAuthenticator(authenticator);
                   ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();         authorizer.setPermissionResolver(new WildcardPermissionResolver());         securityManager.setAuthorizer(authorizer);
                   DruidDataSource ds = new DruidDataSource();         ds.setDriverClassName("com.mysql.jdbc.Driver");         ds.setUrl("jdbc:mysql://localhost:3306/shiro");         ds.setUsername("root");         ds.setPassword("");
          JdbcRealm jdbcRealm = new JdbcRealm();         jdbcRealm.setDataSource(ds);         jdbcRealm.setPermissionsLookupEnabled(true);         securityManager.setRealms(Arrays.asList((Realm) jdbcRealm));
                            SecurityUtils.setSecurityManager(securityManager);
          Subject subject = SecurityUtils.getSubject();
          UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");         subject.login(token);
          Assert.assertTrue(subject.isAuthenticated());     }
  |