Apache Shiro用法初探

来源:互联网 发布:面膜纸 知乎 编辑:程序博客网 时间:2024/04/29 05:15

[每写一次博客,都是对自己学习的一个总结,也希望能帮到遇到同样问题的人]
首先说一下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;@Controllerpublic 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这些是必须的,其他的什么最近登录时间、错误重试次数等等,你能想到必须的都可以写进去。

0 0
原创粉丝点击