码迷,mamicode.com
首页 > Web开发 > 详细

Apache Shiro用法初探

时间:2016-05-13 04:19:53      阅读:204      评论:0      收藏:0      [点我收藏+]

标签:

[每写一次博客,都是对自己学习的一个总结,也希望能帮到遇到同样问题的人]
首先说一下why apache shiro
最近在学着从前端到后端做一个网站来玩,那要搭建一个网站,用户和权限系统肯定是很重要的了。首先是权限系统,可以自己实现一个简单的控制,也可以使用开源的框架。由于自己是学习阶段,所以还是参考开源的框架比较好。网上搜索过后,目前用的比较多的两个开源框架就是apache shiro和spring-security。 Spring-security的功能比较强大,但是配置比较麻烦,显然,这两者是矛盾的。shiro配置比较简单,虽然功能没那么强大,但是也提供了很多可以自己定制的接口。经过两者的对比,决定还是选用shiro。2016-05-07 Version 1.0

这是第一阶段,只实现了基本的用户验证功能。

首先,pom配置,这个就不用多说了。

<span style="font-family: Arial, Helvetica, sans-serif;">     <dependency></span>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
    </dependency>


接下来Spring配置:

<?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">
       <bean id="lifecyleBeanPostProcessor"
             class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

       <bean id="shiroDbRealm" class="org.rikey.web.biz.shiro.ShiroDBRealm">                   // 配置shiro的权限管理器为DB数据库的权限管理器,<span style="color:#ff0000;">需要自己实现</span>,后面会讲
              <property name="credentialsMatcher">                                             // 配置密码校验器
                     <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                            <property name="hashAlgorithmName" value="SHA-256"/>
                            <property name="storedCredentialsHexEncoded" value="false"/>
                     </bean>
              </property>
       </bean>

       <bean id="cacheManager"
             class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>

       <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">         // security manager
              <property name="realm" ref="shiroDbRealm"/>
              <property name="cacheManager" ref="cacheManager"/>
       </bean>
       <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">             // 权限过滤器,很多都见名知意了
              <property name="securityManager" ref="securityManager"/>
              <property name="loginUrl" value="/login.htm"/>
              <property name="unauthorizedUrl" value="/403.htm"/>
              <property name="filterChainDefinitions">
                     <value>
                            /login*=anon                               // anon 匿名, authc 需要鉴权
                            /dologin*=anon
                            /register.htm=anon
                            /doregister=anon
                            /logout*=anon
                            /css/**=anon
                            /js/**=anon
                            /user/**=anon
                            /**=authc

                     </value>
              </property>
       </bean>
       <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>                  // 这两个照着配就行了
       <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
              <property name="securityManager" ref="securityManager"/>
       </bean>

</beans>

第三步,web.xml,这里面还需要配置

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecyle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
配置shiro的filter,对所有的uri进行过滤


第四步:接下来,就需要编码了。

1.注意到前面使用了一个ShiroDBRealm,这个是我们自己根据shiro的接口实现的一个基于数据库的鉴权授权类。

2.另外,shiro只提供了鉴权授权,对于用户注册的时候,密码的保存机制,必须和ShiroDBRealm中用到的密码加密机制一样。(当然,你可以保存明文,就不需要特殊处理用户注册时候的密码了)

1. ShiroDBRealm

package org.rikey.web.biz.shiro;

import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.rikey.web.biz.shiro.result.UserLoginResult;
import org.rikey.web.dao.UserDao;
import org.rikey.web.domain.User;

import javax.annotation.Resource;


public class ShiroDBRealm extends AuthorizingRealm {

    @Resource(name = "userDao")
    private UserDao userDao;

    private static final String REALM_NAME = "shiroDbRealm";

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String)super.getAvailablePrincipal(principals);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Subject currentUser = SecurityUtils.getSubject();

        if (null != currentUser) {
            Session session = currentUser.getSession();
            if (session != null) {
                UserLoginResult user = (UserLoginResult)session.getAttribute("currentUser");
                authorizationInfo.addStringPermissions(user.getPermissions());
                return authorizationInfo;
            }
        }

        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
        String userName = usernamePasswordToken.getUsername();
        User user = userDao.queryUser(userName);

        if (user == null || user.getPassword() == null) {
            return null;
        }

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), REALM_NAME);

        return authenticationInfo;
    }
}
doGetAuthorizationInfo方法先不看,这个是用来获取授权信息的(用户对各个uri是否有权限的信息),这次还没涉及到这个。

重点看doGetAuthencationInfo方法,调用方(shiro框架里的方法来调用)会传入一个token,里面会包含用户名,我们在这个方法中要做的事就是根据框架传进来的用户名,到数据库中去获取该用户的密码,salt值,然后封装成一个SimpleAuthenticationInfo对象,传出去就行了,其他事情就交给我们前面在spring中配置的HashedCredentialsMather来做了。(其实到这里,你可以发现了,shiro里面很多东西,我们都可以自己实现的,比如这个CredentialsMatcher,可以实现我们自己的密码校验逻辑,可以在里面检查密码错误次数等)。

第5步:

有了前面的基础工作,现在就可以在自己的web项目中使用shiro来进行鉴权了。

在处理登录请求的controller中:

@RequestMapping(value = {"/dologin.htm"}, method = RequestMethod.POST)
    public String doLogin(String userName, String password,Boolean remindMe, Model model) {
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        remindMe = remindMe == null ? false : true;
        token.setRememberMe(remindMe);
        Subject subject = SecurityUtils.getSubject();
        String msg;
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                return "redirect:/";
            } else {
                model.addAttribute("msg", "用户名或密码错误");
                return "login";
            }
        } catch  (IncorrectCredentialsException e) {
            msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (ExcessiveAttemptsException e) {
            msg = "登录失败次数过多";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (LockedAccountException e) {
            msg = "帐号已被锁定. The account for username " + token.getPrincipal() + " was locked.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (DisabledAccountException e) {
            msg = "帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (ExpiredCredentialsException e) {
            msg = "帐号已过期. the account for username " + token.getPrincipal() + "  was expired.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (UnknownAccountException e) {
            msg = "帐号不存在. There is no user with username of " + token.getPrincipal();
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (UnauthorizedException e) {
            msg = "您没有得到相应的授权!" + e.getMessage();
            model.addAttribute("msg", msg);
            System.out.println(msg);
        }

        return "login";
    }

首先,注意到代码中有一句: Subject subject = SecurityUtils.getSubject();    SecurityUtils是Shiro的一个工具类,可以获取到很多跟用户登录信息相关的东西。

当前获取了一个subject,这个subject可以获取当前用户的登录信息等,这里直接调用subject的login就行了。

注意login的参数,是一个UserPasswordToken,看到没,和我们前面在ShiroDBRealm中校验密码时用到的UserPasswordToken是同一个类。也就是说,后面校验密码时的对象,是从这里传进去的。


OK,到了这里,好像大功告成了。

纳尼,数据库中还没有用户数据?这可没法玩啊,也没法校验我们写的这么多代码是不是OK的。


if  你只是为了测验一下

      可以直接在ShiroDBRealm的获取鉴权信息那个方法中将用户名、加密后的密码、salt写死返回就行了。

else

     接下来就需要实现一个注册用户的controller,将用户数据写入数据库。

package org.rikey.web.controller;

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.rikey.web.dao.UserDao;
import org.rikey.web.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import java.util.Random;

@Controller
public class Register {

    @Resource(name = "userDao")
    private UserDao userDao;

    private static final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    private static final int SALT_LENGTH = 6;

    @RequestMapping(value = "/register.htm", method = RequestMethod.GET)
    public String register(){
        return "register";
    }

    @RequestMapping(value = {"/doregister"}, method = RequestMethod.POST)
    public String doregister(User user, Model model) {

        try {
            String password = user.getPassword();

            Random random = new Random();
            random.setSeed(System.nanoTime());

            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < SALT_LENGTH; i ++) {
                int number = random.nextInt(CHARS.length());
                sb.append(CHARS.charAt(number));
            }

            String salt = sb.toString();
            String hashedPasswordBase64 = new Sha256Hash(password, salt).toBase64();
            user.setPassword(hashedPasswordBase64);
            user.setSalt(salt);

            userDao.addUser(user);
            return "redirect:/login.htm";
        } catch (Exception e) {
            model.addAttribute("errormsg", "添加用户失败");
            return "error";
        }

    }
}



OK,注意,这里没有用到任何shiro框架结构,只用到了shiro的一个计算hash密码的方法。具体采用什么方法是跟前面的密码校验算法有关的,所以这里可以抽取出一个公共类,做成策略模式,在这里调用。

看前面的shiro spring配置,密码校验是采用的sha256算法,所以我们这里也采用Sha256Hash来进行加密。注意salt是随机生成的。OK,把这些数据写入数据库就行了。
至于数据库中User表的结构就不用多说了吧?id,用户名,密码,salt这些是必须的,其他的什么最近登录时间、错误重试次数等等,你能想到必须的都可以写进去。

Apache Shiro用法初探

标签:

原文地址:http://blog.csdn.net/rikey111/article/details/51336001

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!