shiro和spring整合, applicationContext-shiro.xml (摘自springrain项目):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
default-lazy-init="false">
<!-- 权限管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 数据库认证的实现 org.springrain.frame.shiro.ShiroDbRealm -->
<property name="realm" ref="shiroDbRealm" />
<!-- session 管理器 -->
<property name="sessionManager" ref="sessionManager" />
<!-- 缓存管理器 -->
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- session管理器 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- URL重写中去掉jsessionId -->
<property name="sessionIdUrlRewritingEnabled" value="false" />
<!-- 超时时间 -->
<property name="globalSessionTimeout" value="${cache.timeout}000" />
<!-- 定时检查失效的session,默认true -->
<property name="sessionValidationSchedulerEnabled" value="true" />
<!-- 删除过期的session,默认true -->
<property name="deleteInvalidSessions" value="true" />
<!-- 相隔多久检查一次session的有效性,使用默认的60分钟 -->
<!--
<property name="sessionValidationInterval" value="${cache.timeout}000" />
-->
<!-- session存储的实现 -->
<property name="sessionDAO" ref="shiroSessionDao" />
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<property name="sessionIdCookie" ref="shareSessionCookie" />
</bean>
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<bean id="shareSessionCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,对应的默认是 JSESSIONID -->
<constructor-arg name="name" value="SHAREJSESSIONID" />
<!-- jsessionId的path为 / 用于多个系统共享jsessionId -->
<property name="path" value="/" />
<!-- more secure, protects against XSS attacks -->
<property name="httpOnly" value="true" />
</bean>
<!-- session存储的实现 -->
<bean id="shiroSessionDao"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />
<!-- 单机session -->
<bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!-- shiro的主过滤器,beanId 和web.xml中配置的filter name需要保持一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"
depends-on="frameperms">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/system/login"/>
<property name="unauthorizedUrl" value="/unauth"/>
<!-- 访问地址的过滤规则,从上至下,从左至右的优先级,如果有匹配的规则,就会返回,不会再进行匹配 -->
<property name="filterChainDefinitions">
<value>
/test/**=anon
/layui/** = noSessionCreation
/js/** = noSessionCreation
/css/** = noSessionCreation
/images/** = noSessionCreation
/upload/**=anon
/ajax/**=user
<!-- 拥有teacher角色才能访问 -->
/teacher/** = firewall,teacherweblogin,roles[teacher]
<!-- 拥有admin:edit权限才能访问 -->
/permit/edit = firewall, perms[admin:edit]
/index = firewall,user,keepone
/system/index =firewall,systemuser,framefwlog,keepone
/logout = firewall,frontuser
/system/logout = firewall,frontuser
/system/**=firewall,systemuser,framefwlog,keepone
/** = firewall,frontuser,framefwlog,keepone,frameperms
</value>
</property>
<!-- 声明自定义的过滤器 -->
<property name="filters">
<map>
<!-- 访问日志记录的过滤器 -->
<entry key="framefwlog" value-ref="framefwlog"></entry>
<!--权限校验的过滤器 -->
<entry key="frameperms" value-ref="frameperms"></entry>
<!-- 前台用户过滤器 -->
<entry key="frontuser" value-ref="frontuser"></entry>
<!-- 后台用户过滤器 -->
<entry key="systemuser" value-ref="systemuser"></entry>
<!-- 踢出上个账户的过滤器 -->
<entry key="keepone" value-ref="keepone"/>
<!-- 静态化 过滤器 -->
<entry key="statichtml" value-ref="statichtml"/>
<!-- 防火墙 过滤器 -->
<entry key="firewall" value-ref="firewall"/>
<!-- 学校项目登录过滤器 -->
<entry key="teacherweblogin" value-ref="teacherweblogin"></entry>
</map>
</property>
</bean>
</beans>
shiro默认过滤器:
user表示用户不一定需要已经通过认证,只需要曾经被shiro记住过登录状态的用户就可以正常发起/home请求,就是前面提到的rememberMe
<!--
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没有参数, 和auth的区别就是如果用户选择rememberme,则下次可以直接访问,登入操作时不做检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
-->
realm的简单实现:
package com.concom.security.infrastructure.shiro;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.concom.security.application.user.UserService;
import com.concom.security.domain.user.Role;
import com.concom.security.domain.user.User;
public class ShiroRealm extends AuthorizingRealm{
private final static Logger LOG = LoggerFactory.getLogger(ShiroRealm.class);
public final static String REALM_NAME = "ShiroCasRealm";
@Autowired
private UserService userService;
public ShiroRealm() {
setName(REALM_NAME); // This name must match the name in the User
// class's getPrincipals() method
// setCredentialsMatcher(new Sha256CredentialsMatcher());
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String username = token.getUsername();
if(LOG.isTraceEnabled()){
LOG.trace("开始认证 "+ username);
}
try {
if(StringUtils.isBlank(username)){
throw new AccountException("can not handle this login");
}
User user = userService.getByUsername(username);
checkUser(user, username);
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
} catch (Exception e) {
throw translateAuthenticationException(e);
}
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)getAvailablePrincipal(principals);
if(LOG.isTraceEnabled()){
LOG.trace("开始授权 "+ username);
}
User user = userService.getByUsername(username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> rolesAsString = user.getRolesAsString();
info.addRoles(rolesAsString);
if(user.hasAuths()){
info.addStringPermissions(user.getAuthAsString());
}
for(Role role : user.getRoles()){
info.addStringPermissions(role.getAuthsAsString());
}
return info;
}
/**
* 异常转换
* @param e
* @return
*/
private AuthenticationException translateAuthenticationException(Exception e) {
if (e instanceof AuthenticationException) {
return (AuthenticationException) e;
}
if(e instanceof DisabledAccountException){
return (DisabledAccountException)e;
}
if(e instanceof UnknownAccountException){
return (UnknownAccountException)e;
}
return new AuthenticationException(e);
}
/**
* 检查用户
* @param user
* @param username
*/
private void checkUser(User user,String username){
if(null == user){
throw new UnknownAccountException(username + " can not find "+username+" from system");
}
if(user.isLocked()){
throw new DisabledAccountException("the account is locked now");
}
}
}
web用户登录核心代码:
Subject currUser = SecurityUtils.getSubject();
// user对象是对用户从页面传递过来的账号和密码参数的封装
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(), rememberMe);
currUser .login(token);
微信用户模拟自动登录:
// 模拟登陆
User user = new User();
user.setId("用户id");
user.setAccount("用户账号");
SimplePrincipalCollection principals = new SimplePrincipalCollection(user,
"ShiroRealm");
WebSubject.Builder builder = new WebSubject.Builder(request, response);
builder.principals(principals);
builder.authenticated(true);
WebSubject subject = builder.buildWebSubject();
ThreadContext.bind(subject);
自定义过滤器:
我们可以继承以上shiro默认过滤器, 重写其方法来实现自定义过滤器; 也可以新建过滤器继承AccessControlFilter抽象类, 重写isAccessAllowed()和onAccessDenied()方法. 还可以继承他们的父类: OncePerRequestFilter.
OncePerRequestFilter抽象类:
自定义过滤器可以继承shiro默认过滤器和AccessControlFilter, 还可以继承他们的父类: OncePerRequestFilter, 重写doFilterInternal()方法即可.
顾名思义: 一次请求只经过一次的过滤器.
shiro过滤器都默认继承此过滤器.
话说回来, 过滤器不都是只经过一次吗? 不一定, servlet版本不同,表现也不同:
在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file=”/index.jsp”%>的情况。
到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。
shiro工具类WebUtils
常用API:
Subject获取及使用
1.获取Subject:
Subject subject = SecurityUtils.getSubject();