Java安全框架Shiro的快速使用

一、 shiro的工作流程

( 图片来自开涛大神的 《跟我学Shiro》)

从图中我们可以得知Subject就是“用户” SecurityManager 这个安全管理中心是负责对Subject进行管理;我们只需要继承

AuthorizingRealm 这个类,重写doGetAuthenticationInfo()-- 登录验证方法和
doGetAuthorizationInfo() -- 鉴权方法 实现逻辑即可

二、 shiro的功能
Shiro的具体功能点如下:

(1)身份认证/登录,验证用户是不是拥有相应的身份; (2)授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; (3)会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的; (4)加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; (5)Web支持,可以非常容易的集成到Web环境; Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率; (6)shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; (7)提供测试支持; (8)允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; (9)记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

三、如何quickStart? 1. 首先我们需要添加Shiro的依赖
<properties>
<shiro.version>1.3.2</shiro.version>
</properties>

<!--=====-->
<!--shiro dependences-->
<!--=====-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>${shiro.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-web</artifactId>
  <version>${shiro.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>${shiro.version}</version>
</dependency>

2.重写 Ralm类 实现登录验证和授权的方法
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.Set;
// 继承 AuthorizingRealm类 以重写授权和验证的方法;
 当拦截到资源是在我们定义的路径时;shiro自动调用相对应的方法
public class ShiroRestRealm extends AuthorizingRealm { private final IAccountService accountService; public ShiroRestRealm(IAccountService accountService) { this. accountService = accountService; }
 
// 支持的Token类型
@Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken || token instanceof UserIDToken; } /** * 验证身份信息,但是这里不负责比对的逻辑,我们只需根据principle(原则)返回AuthenticationInfo(携带了用户名和密码) * 比对的逻辑将由shiro框架完成 * * 因为无状态,我们每次都要验证 * @param token * @return * @throws AuthenticationException */ @Override // 登录验证方法 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { SimpleAuthenticationInfo authInfo = null; if (token instanceof UsernamePasswordToken) { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String account = upToken.getUsername(); final String passwd = accountService.findPasswd(account); authInfo = new SimpleAuthenticationInfo(account, passwd, getName()); } else if (token instanceof UserIDToken){ UserIDToken userIDToken= (UserIDToken) token; authInfo= new SimpleAuthenticationInfo(userIDToken.getUserId(),userIDToken.getUserId(),getName()); } return authInfo; } /** * 鉴权,无需比对 * 只需根据用户标识,返回其拥有的资源权限组合,比对的逻辑将由shiro框架完成。 * @param principals * @return */ //权限验证方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo(); //userid String userId = (String) super.getAvailablePrincipal(principals); //userid可访问的所有url的集合 Set<String> permissions = accountService.findPermissionsById(userId); //添加至AuthorInfo simpleAuthorInfo.addStringPermissions(permissions); return simpleAuthorInfo; }}

三、 Shiro的配置
我们使用了Java代码的方式去配置Shiro 我们也可以使用XML的形式在Spring-shiro.XML中配置我们需要配置的内容
使用Java代码配置Shiro的方式:

@ImportResource("classpath:spring-context.xml")
@Configuration  // 可以认为这是spring配置的Java版
public class ShiroConfig {
  /**<bean id="shiroFilter" class="ShiroFilterFactoryBean" />
   * 总过滤器
   * @param securityManager 依赖的安全管理器
   * @param authcFilter 自定义权限过滤器
   * */
  @Bean
  @Autowired
  public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, StatelessAuthcFilter authcFilter) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    /*<!-- securityManager -->
        <property name="securityManager" ref="securityManager" />*/
    bean.setSecurityManager(securityManager);

    /*注册实际的拦截器*/
    Map<String, Filter> filters = bean.getFilters();
    filters.put("stateLessAuthcFilter", authcFilter);// 自定义的无状态http权限验证过滤器
    filters.put("anon", new AnonymousFilter());// shiro提供不做任何处理的过滤器
    filters.put("noSessionCreation", new NoSessionCreationFilter());// shiro提供的过滤器

    // bean.setFilters(filters);
    /*登录跳转链接*/
    bean.setLoginUrl("/rbac/account/login");
    bean.setUnauthorizedUrl("/rbac/account/unauthorized");

    /*-------------*/
    /*不同的url用不同的过滤器拦截*/
    bean.getFilterChainDefinitionMap().put("/rbac/account/login", "anon"); // 不拦截的写在前面
    bean.getFilterChainDefinitionMap().put("/rbac/account/unauthorized*", "anon"); // 不拦截的写在前面
    // 生产环境开启权限和身份验证
    if (System.getProperty("spring.profiles.active") == null ||
        System.getProperty("spring.profiles.active").equals("production")) {
      // 其余所有路径都会被拦截
      bean.getFilterChainDefinitionMap().put("/**", "noSessionCreation,stateLessAuthcFilter");
      // 添加路径拦截
    } else {
      bean.getFilterChainDefinitionMap().put("/**", "anon");
    }

    return bean;
  }

  @Bean
  public StatelessAuthcFilter authcFilter(AccountService accountService) {
    StatelessAuthcFilter authcFilter = new StatelessAuthcFilter();
    authcFilter.setAccountService(accountService);
    return authcFilter;
  }

  @Bean
  @Autowired
  public DefaultWebSecurityManager securityManager(Realm realm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);

    final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionIdCookieEnabled(false);
    sessionManager.setSessionValidationSchedulerEnabled(false);
    securityManager.setSessionManager(sessionManager);
    // 禁止session被创建
    securityManager.setSubjectFactory(new DefaultWebSubjectFactory() {
      @Override
      public Subject createSubject(SubjectContext context) {
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
      }
    });
    // 禁用使用Sessions 作为存储策略的实现
    final DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    subjectDAO.setSessionStorageEvaluator(new DefaultSessionStorageEvaluator() {
      @Override
      public boolean isSessionStorageEnabled() {
        return false;
      }
    });
    securityManager.setSubjectDAO(subjectDAO);
    SecurityUtils.setSecurityManager(securityManager);

    return securityManager;
  }

  @Bean
  @Autowired
  public Realm realm(@Qualifier("accountService") IAccountService accountService) {
    return new ShiroRestRealm(accountService);
  }

  @Bean
  public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }

  @Bean
  public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
    daap.setProxyTargetClass(true);
    return daap;
  }

  @Bean
  @Autowired
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
    aasa.setSecurityManager(securityManager);
    return aasa;
  }


}


XML配置方式如下、

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans   
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"  
    default-lazy-init="true">  

    <description>Shiro Configuration</description>  

    <!-- Shiro's main business-tier object for web-enabled applications -->  
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
        <property name="realm" ref="myShiroRealm" />  
        <property name="cacheManager" ref="cacheManager" />  
    </bean>  

    <!-- 項目自定义的Realm -->  
    <bean id="myShiroRealm" class="com.luo.shiro.realm.MyShiroRealm">  
        <property name="cacheManager" ref="cacheManager" />  
    </bean>  

    <!-- Shiro Filter -->  
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
        <property name="securityManager" ref="securityManager" />  
        <property name="loginUrl" value="/login.jhtml" />  
        <property name="successUrl" value="/loginsuccess.jhtml" />  
        <property name="unauthorizedUrl" value="/error.jhtml" />  
        <property name="filterChainDefinitions">  
            <value>  
                /index.jhtml = authc  
                /login.jhtml = anon
                /checkLogin.json = anon  
                /loginsuccess.jhtml = anon  
                /logout.json = anon  
                /** = authc  
            </value>  
        </property>  
    </bean>  

    <!-- 用户授权信息Cache -->  
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  

    <!-- AOP式方法级权限检查 -->  
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  
        depends-on="lifecycleBeanPostProcessor">  
        <property name="proxyTargetClass" value="true" />  
    </bean>  

    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
        <property name="securityManager" ref="securityManager" />  
    </bean>  

</beans>  
注意以上两种方式由于细节问题 并不完全等同, 仅供参考;

这里有必要说清楚”shiroFilter” 这个bean里面的各个属性property的含义:

(1)securityManager:这个属性是必须的,没什么好说的,就这样配置就好。
(2)loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面,可配置也可不配置。
(3)successUrl:登录成功默认跳转页面,不配置则跳转至”/”,一般可以不配置,直接通过代码进行处理。
(4)unauthorizedUrl:没有权限默认跳转的页面。
(5)filterChainDefinitions,对于过滤器就有必要详细说明一下:

1)Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时),故filterChainDefinitions的配置顺序为自上而下,以最上面的为准

2)当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称

3)通常可将这些过滤器分为两组:

anon,authc,authcBasic,user是第一组认证过滤器

perms,port,rest,roles,ssl是第二组授权过滤器

注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的
user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe

说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc

4)举几个例子
/admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起’/admin’请求
/edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起’/edit’请求
/home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起’/home’请求

5)各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配)
/admins/**=anon 无参,表示可匿名使用,可以理解为匿名用户或游客
/admins/user/**=authc 无参,表示需认证才能使用
/admins/user/**=authcBasic 无参,表示httpBasic认证
/admins/user/**=user 无参,表示必须存在用户,当登入操作时不做检查
/admins/user/**=ssl 无参,表示安全的URL请求,协议为https
/admins/user/*=perms[user:add:]
参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/*=perms[“user:add:,user:modify:*”]
当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法
/admins/user/**=port[8081]
当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数
/admins/user/**=rest[user]
根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等
/admins/user/**=roles[admin]
参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/**=roles[“admin,guest”]
当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法


上文参考了

http://blog.csdn.net/u013142781/article/details/50629708

http://www.cppblog.com/guojingjia2006/archive/2014/05/14/206956.html,更多详细说明请访问该链接。


 
3. web.xml配置 shiro的过滤器

<filter>
  <filter-name>shiroFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  <init-param>
    <param-name>targetFilterLifecycle</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>shiroFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
4. 接下来我们写Controller层代码就好了
@RequestMapping(value = "/login",method = {RequestMethod.GET,RequestMethod.POST})
@ApiOperation(value = "登录验证", /*httpMethod = "GET",*/ /*response = Result.class,*/ notes = "登录验证")
public Result login(Account account) {
  if (null == account.getAccount()) {
    return ResultGenerator.genFailResult("请登录");
  } else {
    try {
      Map<String, Object> resultMap = new HashMap<>();
//check 会报错 这个时候去调用自定义Ralm中的验证的方法;如果验证不通过,会抛出异常,如果捕捉到异常则认为验证失败
     String serverToken = check(account);
      Integer userid = accountService.saveToken(account, serverToken);
      resultMap.put("userid", userid);
      resultMap.put("token", serverToken);
      return ResultGenerator.genSuccessResult(resultMap);
    } catch (AuthenticationException e) { // 失败
      return ResultGenerator.genFailResult("登录失败");
    }
  }

private String check(Account account) {
  final String password = account.getPassword(); // 明文密码
  /*数据库中存储的是md5+盐加密后的密码,因此这里要把加密后的密码传入*/
  final String md5Password = MD5Util.md5(password, SysConst.SALT);
  AuthenticationToken token = new UsernamePasswordToken(account.getAccount(), md5Password);
  Subject currentSubject = SecurityUtils.getSubject();
  currentSubject.login(token);// 这句话触发框架去访问Realm的doGetAuthenticationInfo
  String serverToken = UUID.randomUUID().toString().replaceAll("-", "");
  return serverToken;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值