1.简介
常用的Java EE安全框架有shiro、spring security。shiro被应用非常广泛,可以集成cas,搭建单点登录系统。spring security则被认为比较重,应用没有shiro广泛。shiro提供用户名、密码验证,及密码的加密存储,会话Session的管理,与web集成,支持HTTPS的拦截。
2.Shiro原理简析
shiro 原理剖析:
shiro的核心是java servlet规范中的filter,通过配置拦截器,使用拦截器链来拦截请求,如果允许访问,则通过。通常情况下,系统的登录、退出会配置拦截器。登录的时候,调用subject.login(token),token是用户验证信息,这个时候会在Realm中doGetAuthenticationInfo方法中进行认证。这个时候会把用户提交的验证信息与数据库中存储的认证信息进行比较,一致则允许访问,并在浏览器种下此次回话的cookie,在服务器端存储session信息。退出的时候,调用subject.logout(),会清除回话信息。
shiro中核心概念介绍:
Filter:
1.AnonymousFilter:通过此filter修饰的url,任何人都可以进行访问,即使没有进行权限认证
2.FormAuthenticationFilter:通过此filter修饰的url,会对请求的url进行验证,如果没有通过,则会重定向返回到loginurl
3.BasicHttpAuthenticationFilter:通过此filter修饰的url,要求用户已经通过认证,如果没有通过,则会要求通过Authorization 信息进行认证
4.LogoutFilter:通过此filter修饰的url,一旦收到url请求,则会立即调用subject进行退出,并重定向到redirectUrl
5.NoSessionCreationFilter:通过此filter修饰的url,不会创建任何会话
6.PermissionAuthorizationFilter:权限拦截器,验证用户是否具有相关权限
7.PortFilter:端口拦截器,不是通过制定端口访问url,将自动将端口重定向到指定端口
8.HttpMethodPermissionFilter:rest风格拦截器,配置rest的访问方式
9.RolesAuthorizationFilter:角色拦截器,未登陆,将跳转到loginurl,未授权,将跳转到unauthorizedUrl
10.SslFilter:HTTPS拦截器,需要以HTTPS的方式进行访问
11.UserFilter:用户拦截器,需要用户已经认证,或已经remember me
Subject:
表示当前操作的主体用户,是一个抽象的概念,Subject可以进行登录、退出、权限判断等动作,通常一个subject与一个线程进行关联。
Realm:
表示验证的数据源,存储用户的安全数据,可以进行用户名和密码的匹配,及用户权限查询。
3.Shiro配置方法
shiro 配置方法:
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<description>Apache Shiro Security Configuration</description>
<!-- Realm实现 -->
<bean id="userRealm" class="com.ttyc.mammon.service.shiro.UserRealm">
<property name="cachingEnabled" value="false"/>
</bean>
<!-- 会话ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="domain" value=".ttyongche.com"/>
<property name="path" value="/"/>
<property name="httpOnly" value="false"/>
<property name="maxAge" value="-1"/>
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.crazycake.shiro.RedisSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
<property name="redisManager" ref="redisManager"/>
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="redisCacheManager"/>
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<bean id="authFilter" class="com.ttyc.mammon.controller.filter.AuthFilter"/>
<bean id="permissionFilter" class="com.ttyc.mammon.controller.filter.PermissionFilter"/>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="auth" value-ref="authFilter"/>
<entry key="perm" value-ref="permissionFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/** = auth
</value>
</property>
</bean>
<!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- cacheManager -->
<bean id="redisCacheManager" class="org.crazycake.shiro.RedisCacheManager">
<property name="redisManager" ref="redisManager" />
</bean>
<!-- shiro redisManager -->
<bean id="redisManager" class="com.ttyc.mammon.service.shiro.RedisManager">
<property name="host" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="expire" value="${redis.expire}" />
<property name="dataBase" value="${redis.database}" />
</bean>
</beans>
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<description>Apache Shiro Security Configuration</description>
<!-- Realm实现 -->
<bean id="userRealm" class="com.ttyc.mammon.service.shiro.UserRealm">
<property name="cachingEnabled" value="false"/>
</bean>
<!-- 会话ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="domain" value=".ttyongche.com"/>
<property name="path" value="/"/>
<property name="httpOnly" value="false"/>
<property name="maxAge" value="-1"/>
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.crazycake.shiro.RedisSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
<property name="redisManager" ref="redisManager"/>
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="redisCacheManager"/>
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<bean id="authFilter" class="com.ttyc.mammon.controller.filter.AuthFilter"/>
<bean id="permissionFilter" class="com.ttyc.mammon.controller.filter.PermissionFilter"/>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="auth" value-ref="authFilter"/>
<entry key="perm" value-ref="permissionFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/** = auth
</value>
</property>
</bean>
<!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- cacheManager -->
<bean id="redisCacheManager" class="org.crazycake.shiro.RedisCacheManager">
<property name="redisManager" ref="redisManager" />
</bean>
<!-- shiro redisManager -->
<bean id="redisManager" class="com.ttyc.mammon.service.shiro.RedisManager">
<property name="host" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="expire" value="${redis.expire}" />
<property name="dataBase" value="${redis.database}" />
</bean>
</beans>
Pom依赖:
<shiro.version>1.2.2</shiro.version>
<shiro.redis.version>2.4.2.1-RELEASE</shiro.redis.version>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro.redis.version}</version>
</dependency>
UserRealm:
继承AuthorizingRealm,重写doGetAuthorization和doGetAuthorication方法。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SysUser user = getUserByName(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysRole sysRole = sysRolePermService.getSysRoleByUserId(user.getId());
addRole(authorizationInfo,sysRole);
List<SysPermission> sysPermissions = sysRolePermService.getSysPermissionByUserId(user.getId());
addPermissions(authorizationInfo,sysPermissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
SysUser user = getUserByName(username);
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
if (user != null){
return new SimpleAuthenticationInfo(user.getUserName(),"", getName());
}
return null;
}
AuthFilter:
AuthFilter继承AccessControllerFilter,需要重写isAccessAllowed和onAccessDenied方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
//支持跨域
supportCrossDomain(httpResponse);
// 对外API,通过Service-Token进行认证
String url = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
if (url.startsWith(HTTP_API_URL)) {
if (validateSign(httpRequest.getHeader("Service-Token"))) {
return true;
}
else {
return false;
}
}
//运营后台通过cookie,token,sid进行认证
Subject currentSubject = SecurityUtils.getSubject();
if (! currentSubject.isAuthenticated()){
SysUser sysTokenUser = loginAuthService.validTokenAuth((HttpServletRequest) request);
SysUser sysCookieUser = loginAuthService.validCookieAuth((HttpServletRequest) request);
if (sysTokenUser == null && sysCookieUser == null){
return false;
}
SysUser sysUser = null;
if (sysTokenUser != null){
sysUser = sysTokenUser;
}else {
sysUser = sysCookieUser;
}
UsernamePasswordToken token = new UsernamePasswordToken(sysUser.getUserName(),"");
currentSubject.login(token);
Session session = currentSubject.getSession();
session.setAttribute("LoginUser",sysUser);
}
//在request里设置登录用户
SysUser currentUser = (SysUser) currentSubject.getSession().getAttribute("LoginUser");
request.setAttribute("loginId", currentUser.getId());
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(json);
return false;
}
RedisManager:
shiro原生不支持redis,只支持ehcache,ehcache只能单机缓存,不适合于大型集群应用。第三方crazycake支持shiro使用redis缓存,配置方式如下:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Set;
public class RedisManager extends org.crazycake.shiro.RedisManager {
private String host = "127.0.0.1";
private int port = 6379;
// 0 - never expire
private int expire = 0;
// timeout for jedis try to connect to redis server, not expire time! In
// milliseconds
private int timeout = 0;
private String password = "";
private int dataBase;
private static JedisPool jedisPool = null;
public RedisManager() {
}
/**
* 初始化方法
*/
public void init() {
if (jedisPool == null) {
if (password != null && "".equals(password.trim())) {
password = null;
}
jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password, dataBase);
}
}
/**
* get value from redis
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
byte[] value = null;
Jedis jedis = jedisPool.getResource();
try {
value = jedis.get(key);
} finally {
jedis.close();
}
return value;
}
/**
* set
*
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = jedisPool.getResource();
try {
jedis.set(key, value);
if (this.expire != 0) {
jedis.expire(key, this.expire);
}
} finally {
jedis.close();
}
return value;
}
/**
* set
*
* @param key
* @param value
* @param expire
* @return
*/
public byte[] set(byte[] key, byte[] value, int expire) {
Jedis jedis = jedisPool.getResource();
try {
jedis.set(key, value);
if (expire != 0) {
jedis.expire(key, expire);
}
} finally {
jedis.close();
}
return value;
}
/**
* del
*
* @param key
*/
public void del(byte[] key) {
Jedis jedis = jedisPool.getResource();
try {
jedis.del(key);
} finally {
jedis.close();
}
}
/**
* flush
*/
public void flushDB() {
Jedis jedis = jedisPool.getResource();
try {
jedis.flushDB();
} finally {
jedis.close();
}
}
/**
* size
*/
public Long dbSize() {
Long dbSize = 0L;
Jedis jedis = jedisPool.getResource();
try {
dbSize = jedis.dbSize();
} finally {
jedis.close();
}
return dbSize;
}
/**
* keys
*
* @param pattern
* @return
*/
public Set<byte[]> keys(String pattern) {
Set<byte[]> keys = null;
Jedis jedis = jedisPool.getResource();
try {
keys = jedis.keys(pattern.getBytes());
} finally {
jedis.close();
}
return keys;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getDataBase() {
return dataBase;
}
public void setDataBase(int dataBase) {
this.dataBase = dataBase;
}
}