实战-Shiro安全框架(一)认证

  Shiro是一个很不错的安全权限框架,可以完成认证、授权、加密、会话管理、缓存等。既能用于javase,也可以用于JavaEE中。这里,笔者以Javaee中为例,讲解一下shiro的第一部分--认证。

  认证,通俗的说,就是我们web系统的登陆。使用shiro框架后,就不用我们向之前那样写较多的对账号密码的逻辑判断校验了,直接调用shiro的相关api即可轻松完成认证。当然,这里面的底层也离不开我们常规的账号密码逻辑判断校验,只是shiro对它们进行了更好地封装与优化。

  首先,我们来了解一下shiro的框架的架构,这样有助于理解shiro框架运转流程。从shiro内部来看,如图所示

从架构图中可以看出,shiro最为核心的是security manager(安全管理器),学过springmvc的话,可以理解它为DispatcherServlet的作用。所有与安全有关的操作,都会委托到它这里来。而subject可以理解shiro的门面,与外部代码直接交互的对象,但真正起作用的是它背后的security manager。subject代表的是当前“用户”,这里的用户可以指真正的用户,也可以是机器人、网络爬虫等一切与当前应用交互的对象。security manager里面包含三个很核心的组成部位,一个是Authenticator(认证器)负责subject认证。另一个是Authorizer(授权器)负责控制subject对资源的访问权限。最后一个是Realm我们可以理解为安全实体数据源,即认证器和授权器进行认证授权时,都会去Realm获取数据。至于其他部件,笔者这里不作讲述,因为,缓存、会话管理等这些在实际开发,有其他方案可以更好的解决,而不采取shiro提供的。

  现在我们来使用shiro的认证来实现我们web系统的登陆功能。准备如下:

1.数据表

2.ssm整合的web工程

3.shiro jar包引入

现在开始具体实现。首先,需要在web.xml配置shiro filter拦截器,用来拦截需要安全控制的URL,然后进行相应的控制。这里笔者顺便提醒一下,shiro filter优先于springmvc的拦截器。

   <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>

第二部,配置shiro核心配置文件applicationContext-shiro.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.xsd">

    <!-- =========================================================
         Shiro Core Components - Not Spring Specific
         ========================================================= -->
    <!-- Shiro's main business-tier object for web-enabled applications
         (use DefaultSecurityManager instead when there is no web environment)-->
    <!--  
    1. 配置 SecurityManager!
    -->     
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    		</list>
        </property>
        
        <property name="rememberMeManager.cookie.maxAge" value="20"></property>
    </bean>

    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
    <!--  
    2. 配置 CacheManager. 
    2.1 需要加入 ehcache 的 jar 包及配置文件. 
    -->     
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.: -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
    </bean>
    
    <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
//认证策略
    	<property name="authenticationStrategy">
<!--
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略-->
    		<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    	</property>
    </bean>

    <!-- Used by the SecurityManager to access security data (users, roles, etc).
         Many other realm implementations can be used too (PropertiesRealm,
         LdapRealm, etc. -->
    <!-- 
    	3. 配置 Realm 
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
    -->     
    <bean id="jdbcRealm" class="cn.yzj.shiro.realm.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
//md5加密
    			<property name="hashAlgorithmName" value="MD5"></property>
//加密次数
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>
   
    <!-- =========================================================
         Shiro Spring-specific integration
         ========================================================= -->
    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don't have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <!--  
    4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. 
    -->       
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->
    <!--  
    5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用. 
    -->     
    <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>

    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
         to wire things with more control as well utilize nice Spring things such as
         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
    <!--  
    6. 配置 ShiroFilter. 
    6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                      若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
    -->     
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/showLogin.action"/>
        <property name="successUrl" value="/list.action"/>
        <property name="unauthorizedUrl" value="/unauthorized.action"/>
        
        <!--  
        	配置哪些页面需要受保护. 
        	以及访问这些页面需要的权限. 
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面. 
        	3). logout 登出.
        	4). roles 角色过滤器
        -->
        <property name="filterChainDefinitions">
            <value>
                /showLogin.action = anon
                /login.action = anon
                /logout.action = logout
                
                /user.action = roles[user]
                /admin.action = roles[admin]
                /list.action = user
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
    </bean>

</beans>

具体配置,笔者已经在注释写的听清楚了,这里概括一下,这里的配置文件,就是要配置最为核心的security manager以及shiro filter。这里涉及到很多一些shiro的配置细节,比如认证策略、url匹配模式以及shiro的过滤器等,已经在配置文件的注释作了介绍,这里不再展开叙述。

到这里,shiro整合到我们的web应用已经完成了。这里讲一下shiro的认证大致流程,前台页面传递用户名和密码到后台controller,后台利用shiro得到subject,subject调用login方法进行登录。而subject会委托给security manager,security manager委托给Authenticator,Authenticator 可能会委托给相应的AuthenticationStrategy(认证策略) 进行多 Realm 身份验证。Authenticator 会把相应的token传入Realm获取当前用户的认证信息比进行匹配认证。

具体代码如下

@Controller
public class UserController {
    @RequestMapping("/login.action")
    public String login(@RequestParam("username")String loginName,
                        @RequestParam("password")String password){
//利用SecurityUtils得到当前用户
        Subject subject = SecurityUtils.getSubject();
//如果当前用户未认证过
        if (!subject.isAuthenticated()){
//组装token
            UsernamePasswordToken token = new UsernamePasswordToken(loginName,password,true);
//设置是否记住我
            token.setRememberMe(true);
            try {
                System.out.println("开始执行登陆");
//调用login方法进行登录
//将会委托给security manager,到realm进行具体的认证匹配
                subject.login(token);
            }catch (AuthenticationException ae){
                ae.printStackTrace();
                System.out.println("登录失败: " + ae.getMessage());
                return "login";
            }
        }
//登录成功,跳转成功页面
        return "list";
    }
}

realm的实现,只需要继承AuthorizingRealm即可。实现两个方法

public class ShiroRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        return null;
    }
}

具体认证实现如下

public class ShiroRealm extends AuthorizingRealm {
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//传入的token转回UsernamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//getPrincipal方法可以获取到唯一身份标识。比如用户名、邮箱等
        String principal = (String) upToken.getPrincipal();
//根据传入的唯一身份标识--用户名到数据库查询该用户信息
        SysUser user = userService.getUserByLoginNmae(principal);
//唯一身份标识也可以使用查询出来的实体对象
        //Object principal = user;
        if (user == null){
            throw new UnknownAccountException("账户不存在!");
        }
//取出从数据库查出来的密码
        Object credentials = user.getPassword();
//盐值加密。
//我们之前shiro的配置文件中配置了加密方式为MD5,但是假如有些人的密码是相同的,则加密后的密码
//就会一样的,为了提高安全性,使用盐值加密,这样,即使密码相同,加密后的密码也会不一样,当然参数
//要选择唯一的,比如principal就最好。
        ByteSource credentialsSalt = ByteSource.Util.bytes(principal);
//最好构建SimpleAuthenticationInfo对象返回即可
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,getName());
        return info;
    }
 @Autowired
    private UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

补充一点,shiro提供异常有如下所示,可根据具体情况而抛出。

到这里,认证就基本实现了。最后总结一下认证步骤,配置shiro文件以及web.xm的lshiro拦截器(这一步是只要是想要使用shiro都要做的),编写realm类,调用login方法。

关于shiro的权限控制,笔者将在下一篇中详细介绍。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值