认识Shiro框架

Shiro三大组件:

SubjectSubject一般来说代表当前登录的用户,我们可以在自己的代码中很容易的获取到Subject对象

SecurityManager它是shiro框架的核心。Subject代表某一个用户,而SecurityManager就是对这些Subject进行管理的对象,在web项目中使用shiro的时候,我们通常在xml文件中配置好SecurityManager对象Shiro用它来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realms当对用户执行认证和授权访问验证时,shiro为从应用配置的Realm中查找用户及权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO,它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须指定一个Realm,用于认证和授权,配置多个Realm是可以的,但至少需要一个,shiro中使用Realms这个概念表示与数据进行交互的那一层,封装了数据源连接的细节,我们可以实现不同的realms来连接不同的数据源,通过realms读取用户数据用于认证和鉴权。


Shiro认证过程:

1、收集实体信息

UsernamePasswordToken token = new UsernamePasswordToken(username, password);UsernamePasswordToken支持最常见的用户名/密码的认证机制,由于它实现了RememberMeAuthenticationToken接口,我们可以通过令牌设置“记住我”的功能。

2、提交实体/凭据信息

Subject currentUser = SecurityUtils.getSubject();currentUser.login(token); 收集了实体/凭据信息之后,我们可以通过SecurityUtils工具类,获取当前的用户,然后通过调用login方法提交认证。

3认证处理

try {  

            System.out.println("对用户[" + username + "]进行登录验证..验证开始");  

            currentUser.login(token);  

            System.out.println("对用户[" + username + "]进行登录验证..验证通过");  

            resultPageURL = "main";  

        }catch(UnknownAccountException uae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,未知账户");  

            request.setAttribute("message_login", "未知账户");  

        }catch(IncorrectCredentialsException ice){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");  

            request.setAttribute("message_login", "密码不正确");  

        }catch(LockedAccountException lae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");  

            request.setAttribute("message_login", "账户已锁定");  

        }catch(ExcessiveAttemptsException eae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");  

            request.setAttribute("message_login", "用户名或密码错误次数过多");  

        }catch(AuthenticationException ae){  

            //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");  

            ae.printStackTrace();  

            request.setAttribute("message_login", "用户名或密码不正确");  

        }


登出操作:

登出操作可以通过调用subject.logout()来删除你的登录信息。

SecurityUtils.getSubject().logout();


授权操作:

授权有着三要素:权限,角色和用户。 

权限:

权限是Apache Shiro安全机制最核心的元素。它在应用程序中明确声明了被允许的行为和表现。一个格式良好好的权限声明可以清晰表达出用户对该资源拥有的权限。

角色:

Shiro支持两种角色模式: 

1、传统角色:一个角色代表着一系列的操作,当需要对某一操作进行授权验证时,只需判断是否是该角色即可。这种角色权限相对简单、模糊,不利于扩展。 

2、权限角色:一个角色拥有一个权限的集合。授权验证时,需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述,适合更复杂的权限设计。


内置的过滤链FilterChain:

如  /admin = authc,roles[admin]      表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求

   /edit = authc,perms[admin:edit]  表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求。


新建工程配置shiro

(1)web.xml配置文件里面加 

<!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->  

    <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->  

    <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->  

    <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->  

    <filter>  

        <filter-name>shiroFilter</filter-name>  

        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  

        <init-param>  

            <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->  

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


(2)Application.xml配置文件里面加 

<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->  

    <bean id="myRealm" class="fy.com.realm.MyRealm"/>    

    <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->  

    <!-- <property name="sessionMode" value="native"/>,详细说明见官方文档 -->  

    <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->  

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

        <property name="realm" ref="myRealm"/>  

    </bean>     

    <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->  

    <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于SpringWeb应用提供了完美的支持 -->  

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  

        <!-- Shiro的核心安全接口,这个属性是必须的 -->  

        <property name="securityManager" ref="securityManager"/>  

        <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->  

        <property name="loginUrl" value="/"/>  

        <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp) -->  

        <!-- <property name="successUrl" value="/system/main"/> -->  

        <!-- 用户访问未对其授权的资源时,所显示的连接 -->  

        <property name="unauthorizedUrl" value="/"/>  

        <!-- Shiro连接约束配置,即过滤链的定义 -->   

        <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->  

        <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do.jsp后面的*表示参数,比方说login.jsp?main这种 -->  

        <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->  

        <property name="filterChainDefinitions">  

            <value>  

                /mydemo/login=anon  

                /mydemo/getVerifyCodeImage=anon  

                /main**=authc  

                /user/info**=authc  

                /admin/listUser**=authc,perms[admin:manage]  

            </value>  

        </property>  

    </bean>     

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>


(3)自定义的Realm

MyRealm.java:

package com.jadyer.realm;      

/** 

 * 自定义的指定Shiro验证用户登录的类 

 * @see 在本例中定义了2个用户:jadyer和玄玉,jadyer具有admin角色和admin:manage权限,玄玉不具有任何角色和权限 

 * @create Sep 29, 2013 3:15:31 PM 

 * @author 玄玉<http://blog.csdn.net/jadyer> 

 */  

public class MyRealm extends AuthorizingRealm {  

    /** 

     * 为当前登录的Subject授予角色和权限 

     * @see 经测试:本例中该方法的调用时机为需授权资源被访问时 

     * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache 

     * @see 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache 

     * @see 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache 

     */  

    @Override  

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){  

        //获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()  

        String currentUsername = (String)super.getAvailablePrincipal(principals);  

        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();   

        if(null!=currentUsername && "jadyer".equals(currentUsername)){  

            //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色    

            simpleAuthorInfo.addRole("admin");  

            //添加权限  

            simpleAuthorInfo.addStringPermission("admin:manage");  

            System.out.println("已为用户[jadyer]赋予了[admin]角色和[admin:manage]权限");  

            return simpleAuthorInfo;  

        }else if(null!=currentUsername && "玄玉".equals(currentUsername)){  

            System.out.println("当前用户[玄玉]无授权");  

            return simpleAuthorInfo;  

        }  

        //若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址  

        //详见applicationContext.xml中的<bean id="shiroFilter">的配置  

        return null;  

    }           

    /** 

     * 验证当前登录的Subject 

     * @see 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时 

     */  

    @Override  

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {  

        //获取基于用户名和密码的令牌  

        //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的  

        //两个token的引用都是一样的,本例中是org.apache.shiro.authc.UsernamePasswordToken@33799a1e  

        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;  

        System.out.println("验证当前Subject时获取到token" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));  

        //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息  

        //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)  

        //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证  

        if("jadyer".equals(token.getUsername())){  

            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("jadyer", "jadyer", this.getName());  

            this.setSession("currentUser", "jadyer");  

            return authcInfo;  

        }else if("玄玉".equals(token.getUsername())){  

            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("玄玉", "xuanyu", this.getName());  

            this.setSession("currentUser", "玄玉");  

            return authcInfo;  

        }  

        //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常  

        return null;  

    }               

    /** 

     * 将一些数据放到ShiroSession,以便于其它地方使用 

     * @see 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到 

     */  

    private void setSession(Object key, Object value){  

        Subject currentUser = SecurityUtils.getSubject();  

        if(null != currentUser){  

            Session session = currentUser.getSession();  

            System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");  

            if(null != session){  

                session.setAttribute(key, value);  

            }  

        }  

    }  

}


(4)LoginController.java部分代码

public String login(HttpServletRequest request){  

        String resultPageURL = InternalResourceViewResolver.FORWARD_URL_PREFIX + "/";  

        String username = request.getParameter("username");  

        String password = request.getParameter("password");  

        //获取HttpSession中的验证码  

        String verifyCode = (String)request.getSession().getAttribute("verifyCode");  

        //获取用户请求表单中输入的验证码  

        String submitCode = WebUtils.getCleanParam(request, "verifyCode");  

        System.out.println("用户[" + username + "]登录时输入的验证码为[" + submitCode + "],HttpSession中的验证码为[" + verifyCode + "]");  

        if (StringUtils.isEmpty(submitCode) || !StringUtils.equals(verifyCode, submitCode.toLowerCase())){  

            request.setAttribute("message_login", "验证码不正确");  

            return resultPageURL;  

        }  

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);  

        token.setRememberMe(true);  

        System.out.println("为了验证登录用户而封装的token" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));  

        //获取当前的Subject  

        Subject currentUser = SecurityUtils.getSubject();  

        try {  

            //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查  

            //每个Realm都能在必要时对提交的AuthenticationTokens作出反应  

            //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法  

            System.out.println("对用户[" + username + "]进行登录验证..验证开始");  

            currentUser.login(token);  

            System.out.println("对用户[" + username + "]进行登录验证..验证通过");  

            resultPageURL = "main";  

        }catch(UnknownAccountException uae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,未知账户");  

            request.setAttribute("message_login", "未知账户");  

        }catch(IncorrectCredentialsException ice){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");  

            request.setAttribute("message_login", "密码不正确");  

        }catch(LockedAccountException lae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");  

            request.setAttribute("message_login", "账户已锁定");  

        }catch(ExcessiveAttemptsException eae){  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");  

            request.setAttribute("message_login", "用户名或密码错误次数过多");  

        }catch(AuthenticationException ae){  

            //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景  

            System.out.println("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");  

            ae.printStackTrace();  

            request.setAttribute("message_login", "用户名或密码不正确");  

        }  

        //验证是否登录成功  

        if(currentUser.isAuthenticated()){  

            System.out.println("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");  

        }else{  

            token.clear();  

        }  

        return resultPageURL;  

    }





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值