1、参考代码
http://git.codeweblog.com/chunanyong/springrain
2、主要说明
(1)SSO,即单点登录认证,采用的是shiro+redis的方式,实现集中式的session管理
(2)鉴权,即权限校验,基于经典的role-user-resource(这里一般指menu)模型,还是采用shiro,自己实现鉴权方法与shiro的securityManager集成即可
3、添加依赖
主要是shiro、redis的依赖
<!--shiro-->
<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>
<!--spring redis as share session-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<!-- Redis Java Driver -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.0</version>
</dependency>
<shiro.version>1.2.3</shiro.version>
<spring-data-redis.version>1.4.0.RELEASE</spring-data-redis.version>
4、xml配置
(1)配置web.xml
<!--add shiro filter-->
<filter>
<!--需要在(parent) context中声明id为shiroFilter的bean-->
<filter-name>shiroFilter</filter-name>
<!-- DelegatingFilterProxy,该类其实并不能说是一个过滤器,它的原型是FilterToBeanProxy,即将Filter作为spring的bean,由spring来管理-->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
(2)配置application-shiro.xml
<?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-4.0.xsd"
default-lazy-init="false" >
<!-- shiro的主过滤器,beanId 和web.xml中配置的filter name需要保持一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 默认的登陆访问url -->
<property name="loginUrl" value="/login" />
<!-- 登陆成功后跳转的url -->
<property name="successUrl" value="/index" />
<!-- 没有权限跳转的url -->
<property name="unauthorizedUrl" value="/unauth" />
<!-- 访问地址的过滤规则,从上至下的优先级,如果有匹配的规则,就会返回,不会再进行匹配 -->
<property name="filterChainDefinitions">
<value>
/js/** = anon
/css/** = anon
/images/** = anon
/unauth = anon
/getCaptcha=anon
/login = anon
/auto/login = anon
/favicon.ico = anon
/index = user
/logout = logout
/system/menu/leftMenu=user
/**/ajax/** = user
/** = user,permissionCheck
</value>
</property>
<!-- 声明自定义的过滤器 -->
<property name="filters">
<map>
<entry key="permissionCheck" value-ref="shiroSSOUpmFilter"></entry>
</map>
</property>
</bean>
<!-- session 集群 -->
<bean id="shiroCacheManager" class="com.persia.shiro.cache.ShiroRedisCacheManager">
<!--在applicationContext-redis.xml里头声明-->
<property name="cached" ref="redisCacheService" />
</bean>
<!-- 权限管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 基于数据库登录校验的实现 com.persia.upm.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">
<!-- 超时时间 -->
<property name="globalSessionTimeout" value="1800000" />
<!-- session存储的实现 -->
<property name="sessionDAO" ref="shiroSessionDao" />
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<property name="sessionIdCookie" ref="sharesession" />
<!-- 定时检查失效的session -->
<property name="sessionValidationSchedulerEnabled" value="true" />
</bean>
<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,对应的默认是 JSESSIONID -->
<constructor-arg name="name" value="SHAREJSESSIONID" />
<!-- jsessionId的path为 / 用于多个系统共享jsessionId -->
<property name="path" value="/" />
</bean>
<!-- session存储的实现 -->
<bean id="shiroSessionDao"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />
</beans>
(3)配置application-redis.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd"
default-lazy-init="false">
<context:property-placeholder location="classpath:config.properties" />
<!--基于redis分布的session共享-->
<bean id="redisCacheService" class="com.persia.shiro.cache.RedisCachedImpl">
<property name="redisTemplate" ref="redisTemplate" />
<property name="expire" value="86400" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
</bean>
</beans>
5、代码
(1)ShiroSSOUpmFilter
import com.persia.Constants;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Service
public class ShiroSSOUpmFilter extends PermissionsAuthorizationFilter {
public Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private CacheManager shiroCacheManager;
@Override
public boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) throws IOException {
//upm with shiro subject/principal
Subject user = SecurityUtils.getSubject();
ShiroUser shiroUser = (ShiroUser) user.getPrincipal();
//get sso session
Session session = user.getSession(false);
Cache<Object, Object> cache = shiroCacheManager.getCache(Constants.SSO_CACHE);
Object cachedSession = cache.get(Constants.SSO_CACHE + "-" + shiroUser.getAccount());
if(cachedSession == null){
user.logout();
return false;
}
String cachedSessionId =cachedSession.toString();
String sessionId = (String) session.getId();
if (!sessionId.equals(cachedSessionId)) {
user.logout();
}
HttpServletRequest req = (HttpServletRequest) request;
//get shiro upm
Subject subject = getSubject(request, response);
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
int i = uri.indexOf(contextPath);
if (i > -1) {
uri = uri.substring(i + contextPath.length());
}
if (StringUtils.isBlank(uri)) {
uri = "/";
}
boolean permitted = false;
if ("/".equals(uri)) {
permitted = true;
} else {
//check has right using shiro
permitted = subject.isPermitted(uri);
}
return permitted;
}
}
(2)ShiroCacheManager即ShiroRedisCacheManager
import org.apache.shiro.cache.AbstractCacheManager;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
public class ShiroRedisCacheManager extends AbstractCacheManager {
private ICached cached;
@Override
protected Cache createCache(String cacheName) throws CacheException {
return new ShiroRedisCache<String, Object>(cacheName,cached);
}
public ICached getCached() {
return cached;
}
public void setCached(ICached cached) {
this.cached = cached;
}
}
(3)ShiroDbRealm
import com.persia.Constants;
import com.persia.service.UpmService;
import com.persia.shiro.ShiroUser;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
//认证数据库存储
@Component("shiroDbRealm")
public class ShiroDbRealm extends AuthorizingRealm {
public Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private UpmService upmService;
@Resource
private CacheManager shiroCacheManager;
public static final String HASH_ALGORITHM = "MD5";
public static final int HASH_INTERATIONS = 1;
private static final int SALT_SIZE = 8;
public ShiroDbRealm() {
// 认证
super.setAuthenticationCacheName(Constants.SSO_CACHE);
super.setAuthenticationCachingEnabled(false);
// 授权
super.setAuthorizationCacheName(Constants.AUTH_CACHE);
super.setName(Constants.AUTH_REALM);
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
// 因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
// (可能是关闭浏览器,或超时),但此时缓存依旧存在(principals),所以会自己跑到授权方法里。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principalCollection);
SecurityUtils.getSubject().logout();
return null;
}
ShiroUser shiroUser = (ShiroUser) principalCollection
.getPrimaryPrincipal();
// String userId = (String)
// principalCollection.fromRealm(getName()).iterator().next();
String userId = shiroUser.getId();
if (StringUtils.isBlank(userId)) {
return null;
}
// 添加角色及权限信息
SimpleAuthorizationInfo sazi = new SimpleAuthorizationInfo();
try {
sazi.addRoles(upmService.getRolesAsString(userId));
sazi.addStringPermissions(upmService.getPermissionsAsString(userId));
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
return sazi;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
/*
* String pwd = new String(upToken.getPassword()); if
* (StringUtils.isNotBlank(pwd)) { pwd = DigestUtils.md5Hex(pwd); }
*/
// 调用业务方法
User user = null;
String userName = upToken.getUsername();
try {
user = upmService.findLoginUser(userName, null);
} catch (Exception e) {
logger.error(e.getMessage(),e);
throw new AuthenticationException(e);
}
if (user != null) {
// 要放在作用域中的东西,请在这里进行操作
// SecurityUtils.getSubject().getSession().setAttribute("c_user",
// user);
// byte[] salt = EncodeUtils.decodeHex(user.getSalt());
Session session = SecurityUtils.getSubject().getSession(false);
AuthenticationInfo authinfo = new SimpleAuthenticationInfo(
new ShiroUser(user), user.getPassword(), getName());
Cache<Object, Object> cache = shiroCacheManager.getCache(Constants.SSO_CACHE);
cache.put(Constants.SSO_CACHE + "-" + userName,session.getId());
return authinfo;
}
// 认证没有通过
return null;
}
/**
* 设定Password校验的Hash算法与迭代次数.
*/
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(
HASH_ALGORITHM);
matcher.setHashIterations(HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
}
这段代码只是本机的实现,对于分布式应用来说,这个应该将upmService改成远程调用的形式。
6、各个系统如何集成
(1)web.xml注册ssoFilter
(2)applicationContext里头注册ssoFilter实现
(3)注入upmService(远程调用形式)
问题:如果是采用原来的shiroFilter这样的话,对于第一二步来说,每个应用都得配置redis和securityManager,这样对系统入侵太大,不够轻量,但是可以充分利用shiro提供的服务。
解决:对于各个系统来说,需要一个ssoFilter,对每个url进行拦截,若需要登录,则取cookie中的sessionId,远程访问shiro/sso server,判断session是否存在,如果存在,则返回继续下一步的鉴权判断,若不存在,则跳转到登录页面。因此,ssoFilter采用正常的servlet filter即可,若需要组合authFilter,则还是采取DelegatingFilterProxy的形式。
(或者看是否可以改造shiroFilter,不注入cacheManager,看是否有问题)
缺点:这样使用的话,其实对shiro的变向实现(对upm的集成进行解耦),可以借鉴shiro部分思路,实现自己的sso/upm server。
相关推荐
SSM(Spring、SpringMVC、MyBatis)与Apache Shiro及Redis的整合是Java开发中常见的安全控制和会话管理解决方案。本项目旨在实现单点登录(Single Sign-On, SSO),允许用户在多个应用系统间进行无感知的身份验证,...
"Shiro+SpringMVC+Redis+MySQL实现单点登录"是一个典型的系统安全架构,它整合了多个技术组件来构建一个高效、可靠的单点登录(Single Sign-On, SSO)解决方案。以下是关于这个主题的详细知识点: 1. **Apache ...
SSM+Shiro+Redis是一个常见的企业级Java Web开发架构,结合了Spring、SpringMVC、Mybatis、Apache Shiro和Redis五种技术,用于构建高效、安全且可扩展的Web应用。下面将详细介绍这些技术及其集成应用。 **Spring** ...
SSM(Spring、SpringMVC、MyBatis)与Apache Shiro及Redis的整合是现代Web应用程序中常见的安全管理和认证架构。这个压缩包文件提供的内容很可能是为了帮助开发者理解和实践如何在项目中实现基于SSM的单点登录...
- 故障恢复:考虑Redis故障时的备份策略,避免单点故障影响SSO服务。 通过上述组件的组合,开发者可以构建一个高效、安全的SSO解决方案,提高用户体验,同时降低系统间的管理复杂度。学习和理解这些技术的集成与...
在本项目中,我们将结合SpringBoot、Redis和Shiro来实现SSO功能,并进行统一的异常处理和日志管理。 首先,SpringBoot是一个基于Spring框架的轻量级开发工具,它简化了新Spring应用的初始搭建以及开发过程。...
OAuth2、Shiro和Redis这三者在Web应用开发中各自扮演着重要的角色。OAuth2是一种开放标准授权框架,用于允许第三方应用安全地访问用户存储在另一服务提供者上的资源;Shiro是Java的安全管理框架,提供身份验证、授权...
由于项目需要从网上搜集的相关的集成框架,很多都是部分集成,一直没有找到整个流程全部集成好的,所以将集成好的框架分享出来供...主要实现SSO、后台RBAC角色认证管理。 下载后需要自行修改配置,项目包内带sql脚本
SSO(Single Sign-On)是单点登录的缩写,它允许用户在多个应用程序中进行身份...通过阅读《原创Shiro整合SSO教程》提供的链接,你可以找到具体的实现细节和代码示例,帮助你在实际项目中顺利实现SSO与Shiro的集成。
civism-sso基于springmvc+redis+shiro 实现的分布式单点登录系统,继承了登录验证以及数据接口鉴权, 能很好的帮助其他项目做前后端分析,并且不需要其他子系统关心权限,并且该项目支持跨域请求 功能介绍 * sso登录...
Apache Shiro是一个强大且易用的Java安全框架,提供了认证、授权、会话管理和加密等功能,非常适合用来实现SSO。在Spring框架基础上整合Shiro、Spring Data Redis以及Spring Session Data Redis,可以构建一个高效、...
通过Shiro,我们可以方便地实现单点登录(SSO)和session的集群共享。 5. **Log4j2**:Log4j2是Apache的一个日志组件,它提供了灵活的日志记录方式,具有高性能和丰富的日志管理功能。Log4j2可以自定义日志级别,...
本项目是一个基于Spring Boot和Shiro框架的单点登录(SSO)系统,旨在为企业应用环境中的多个应用系统提供统一的用户登录平台。通过SSO系统,用户只需登录一次即可访问所有相互信任的应用系统,无需重复输入用户名和...
该项目利用了基于springboot + vue + mysql的开发模式框架实现的课设系统,包括了项目的源码资源、sql文件、相关指引文档等等。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理...
目前已经完成:MongoDB,Netty,Nginx,MySQL,Java,Redis,Shiro,Solr,SpringBoot,SpringData,SSO,Mybatis,Kotlin,还在持续更新中.zip 每天都在进步,每周都在总结,Java架构师成长之路。目前已经完成:...
至于单点登录(SSO),Shiro提供了RememberMe服务,结合Redis存储Session,可以实现跨域登录状态保持。当用户在一个系统登录后,其他系统可以通过验证RememberMe令牌来确认用户身份,从而实现单点登录。 总结来说,...
总结,基于SpringBoot和Shiro的单点登录系统结合了两者的优点,为开发者提供了一套高效、易用的安全解决方案。通过理解SSO的原理,熟练掌握SpringBoot和Shiro的使用,可以构建出满足实际需求的高质量系统。在实际...
单点登录(Single Sign-On,简称SSO)是一种网络访问控制机制,允许用户在一次登录后,无需再次认证即可访问多个相互信任的应用系统。在这个"SSO单点登录demo"中,我们主要探讨如何利用Java Servlet技术来实现这一...