您的位置:首页 > 运维架构 > Apache

Apache Shiro用法初探

2016-05-07 08:27 881 查看
[每写一次博客,都是对自己学习的一个总结,也希望能帮到遇到同样问题的人]

首先说一下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这些是必须的,其他的什么最近登录时间、错误重试次数等等,你能想到必须的都可以写进去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: