【前言】
Apache Shiro是Java的一个安全框架。主要用于权限控制,简单易用。之前单看shiro始终理会不了它的思想,后来在网上发现某智的一个bos项目中运用到了shiro,果断学习,看完后略有心得,以记之。
【项目简介】
该bos项目主要是一个后台管理项目,采用传统ssh技术架构,使用spring与shiro进行整合做权限拦截,以下是项目中权限部分的笔记。
首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作
Subject:主体,代表了当前“用户”
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限)
用户主体访问系统,由安全管理器进行权限验证,安全管理器从Realm域中获取到该用户的角色权限,并作出相应的权限反馈,Realm域就是一个Dao,主要获取用户的角色权限数据并交给安全管理器。所以整个流程中,安全管理器是核心角色。
【1】web.xml中配置shiro的过滤器
shiro的过滤器就类似于struts2的核心过滤器一般
<!-- shiro过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:这里filterName的值可以随便起,无要求。但是在spring注入时要保持一致
【2】将shiro过滤器注入spring
<!-- 配置工厂bean,用于创建shiro框架用到过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入当前系统的登录页面 -->
<property name="loginUrl" value="/login.jsp"></property>
<!-- 注入成功页面 -->
<property name="successUrl" value="/index.jsp"></property>
<!-- 注入权限不足页面 -->
<property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
<!-- 注入url拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/images/** = anon
/js/** = anon
/login.jsp* = anon
/validatecode.jsp* = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff"]<!-- roles角色集,perms权限集 -->
/* = authc
</value>
<!--
/page_base_staff.action = roles["staff"]//要访问此action必须有staff这个角色
/page_base_staff.action = perms["staff"]//要访问此action必须有staff这个权限
-->
</property>
</bean>
注意:以上属性均以set注入,查看ShiroFilterFactoryBean源码便知,业务需要配哪个属性就配哪个,非必要配置,这里页面以/开头均在webroot目录下。
这里bean 的id属性要与web.xml中shiro的过滤器名称一致
url拦截规则:一般图片、JS、CSS样式不拦截,直接设置anon角色权限即可(/css/** = anon)
具体url拦截:
/page_base_staff.action = perms["staff"]//访问此action需要staff权限
/page_base_staff.action =roles["staff"]//访问此action需要staff角色(角色是权限的集合)
要拦截的路径:/* = authc
【3】编写自定义Realm域并注入到spring中
自定义Realm需要继承AuthorizingRealm,实现其认证 授权方法
/**
* ClassName: BOSRealm
* @author lvfang
* @Desc: 自定义realm
* @date 2017-8-23
*/
public class BOSRealm extends AuthorizingRealm {
@Resource
private IUserDao userDao;
/**
* 认证方法(是否有这个用户)
*/
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("进入认证方法... ...");
UsernamePasswordToken upToken = (UsernamePasswordToken) token;//token令牌强转
String username = upToken.getUsername();//从令牌中得到用户名
//查询用户
User user = userDao.findUserByUsername(username);
if(user == null){
//用户名不存在
return null;
}else{
//用户名存在
String password = user.getPassword();
// 创建简单认证信息对象
/***
* 参数一:签名,程序可以在任意位置获取当前放入的对象
* 参数二:从数据库中查询出的密码
* 参数三:当前realm的名称
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,this.getClass().getSimpleName());
//返回给安全管理器,由安全管理器负责比对数据库中查询出的密码和页面提交的密码
return info;
}
}
/**
* 授权方法(这个用户有什么权限)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("进入授权方法... ...");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("staff");//为当前用户授予staff权限
info.addRole("staff");//为当前用户授予staff角色(角色是权限的集合)
//TODO 根据当前登录用户查询数据库,获取其对应的权限数据
return info;
}
}
注入spring
<!-- 注册自定义realm -->
<bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
【4】注入安全管理器,并将realm注入给安全管理器
<!-- 注入 securityManager管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 将自定义realm注入给securityManager管理 -->
<property name="realm" ref="bosRealm"></property>
</bean>
【5】在login方法中做处理
/**
* 登陆方法(shiro版)
* @return
*/
public String login(){
//判断验证码
String code = (String) this.setSession("key");
//判断验证码是否正确
if(StringUtils.isNotBlank(checkcode)&& checkcode.equals(code)){
//获取当前用户对象
Subject subject = SecurityUtils.getSubject();//目前为"未认证状态"
String password = model.getPassword();
password = MD5Utils.md5(password);
//构造一个用户名密码令牌
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(),password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
//登陆失败 设置提示信息,跳转登陆页面
this.addActionError("用户名不存在!");
return "login";
} catch (Exception e) {
e.printStackTrace();
//登陆失败 设置提示信息,跳转登陆页面
this.addActionError("用户名或密码错误!");
return "login";
}
//获取认证信息对象中存储的User对象
User user = (User) subject.getPrincipal();
this.getSession().setAttribute("loginUser", user);//用户存入session
return "home";
}else{
//登陆失败,验证码失败 跳转至登陆页面
this.addActionError("验证码错误!");
return "login";
}
}
这里根据username和password构造一个用户名令牌,当subject主体去调用login()方法登陆时,会执行Realm域中的认证和授权方法。
【附加】
1:
shiro过滤器属性含义
securityManager:这个属性是必须的。
loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl :没有权限默认跳转的页面
2:
其权限过滤器及配置释义
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles(角色):例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms(权限):例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查