文档章节

基于Shiro和Redis的SSO及鉴权服务

xixicat
 xixicat
发布于 2015/02/17 09:03
字数 1684
阅读 941
收藏 16
点赞 1
评论 3

1、参考代码

http://git.oschina.net/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。

 


© 著作权归作者所有

共有 人打赏支持
xixicat
粉丝 61
博文 201
码字总数 129524
作品 0
深圳
程序员
加载中

评论(3)

fsl111
fsl111
麻烦下次粘贴的时候,把该整的整理好了再说!这几把缺这个缺哪个的!
zqb666
zqb666
有示例源码吗 能不能给我发下 6637152@qq.com 谢谢
zqb666
zqb666
有示例源码吗 能不能给我发下 6637152@qq.com 谢谢
shiro jwt 构建无状态分布式鉴权体系

一:JWT 1、令牌构造 JWT(json web token)是可在网络上传输的用于声明某种主张的令牌(token),以JSON 对象为载体的轻量级开放标准(RFC 7519)。 一个JWT令牌的定义包含头信息、荷载信息、签...

wangjie2016
2017/11/22
0
0
分布式认证系统--mutouren-zero

mutouren-zero 是一套分布式认证、组织、权限系统,专门设计对多个应用提供身份认证、鉴权服务,采用 SSO 方式,为多个应用提供统一登录入口,避免各应用重复开发"组织机构"模块。 解决目前软...

匿名
2017/07/11
543
0
Shiro系列(3) - What is shiro?

什么是shiro? Shiro是apache的一个开源权限管理的框架,它实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架 使用shiro来实现权限管理,可以非常有效的提高...

风间影月
2017/10/25
0
0
使用shiro保护你的springboot应用

springboot中使用shiro大都是通过shiro-spring.jar进行的整合的,虽然不是太复杂,但是也无法做到spring-boot-starter风格的开箱即用。项目中经常用到的功能比如:验证码、密码错误次数限制、...

wangjie2016
01/05
0
1
认证鉴权与API权限控制在微服务架构中的设计与实现(一)

作者: [Aoho’s Blog] 引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。 1. 背景 最近在做...

嘿嘿!!
2017/12/11
0
0
搭建基于OAuth2和SSO的开放平台

原创文章,转载或摘录请说明文章来源,谢谢! http://heartlifes.com/%E6%90%AD%E5%BB%BA%E5%9F%BA%E4%BA%8Eoauth2%E5%92%8Csso%E7%9A%84%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0/ 原创文章,转......

吕伯文
06/22
0
0
Spring Security

重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建可以这样简单重拾后端之Spring Boot(二):MongoDb的无缝集成重拾后端之...

掘金官方
01/04
0
0
Guns v3.0 发布,模块化,新增 REST 服务

Guns V3.0更新说明 单模块拆分成如下多模块,guns-parent(maven父项目),guns-core(guns基础模块),guns-admin(guns后台管理系统),guns-rest(rest服务模块) 新增REST API服务,用于提供REST接口 ...

naan1993
2017/08/28
1K
4
将 Shiro 作为应用的权限基础

Shiro 是 Java 世界中新近出现的权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。本文介绍了 Shiro 的关键概念和权限模型,同时...

heroShane
2014/02/26
0
0
基于hmac的rest api鉴权处理

一:常见的HTTP鉴权协议 REST表述性状态转移(Representational State Transfer),是基于HTTP的web服务设计风格,一个 RESTFUL API 是无状态的,这意味着认证请求应当不能依赖于cookie或ses...

wangjie2016
2017/05/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MyBatis源码解读之延迟加载

1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延...

无忌
7分钟前
0
0
javascript 类变量的实现

代码如下: function echo(){ for(let i=0;i<arguments.length;i++) console.log(arguments[i]);}function extend(o, p){for (prop in p) {o[prop] = p[prop]}retur......

backbye
11分钟前
0
2
编程语言对比分析:Python与Java和JavaScript(图)

编程语言对比分析:Python与Java和JavaScript(图): 凭什么说“Python 太慢,Java 太笨拙,我讨厌 JavaScript”?[图] 编程语言生而为何? 我们人类从原始社会就是用语言表达自己,互相沟通...

原创小博客
19分钟前
0
0
Akka构建Reactive应用《one》

看到这Akka的官网,描述使用java或者scala构建响应式,并发和分布式应用更加简单,听着很高级的样子,下面的小字写着消息驱动,但是在quickstart里面又写容错事件驱动,就是这么钻牛角尖。 ...

woshixin
31分钟前
0
0
ffmpeg源码分析 (四)

io_open 承接上一篇,对于avformat_open_input的分析还差其中非常重要的一步,就是io_open,该函数用于打开FFmpeg的输入输出文件。 在init_input中有这么一句 if ((ret = s->io_open(s, &s-...

街角的小丑
32分钟前
0
0
String,StringBuffer ,StringBuilder的区别

不同点 一、基类不同 StringBuffer、StringBuilder 都继承自AbStractStringBuilder,String 直接继承自 Object 2、底层容器“不同” 虽然底层都是字符数组,但是String的是final修饰的不可变...

不开心的时候不要学习
47分钟前
0
0
nodejs 文件操作

写文件code // 加载文件模块var fs = require("fs");var content = 'Hello World, 你好世界!';//params 文件名,内容,编码,回调fs.writeFile('./hello.txt',content,'utf8',function (er......

yanhl
50分钟前
0
0
SpringBoot mybits 查询为0条数据 但是在Navicat 中可以查询到数据

1.页面请求: 数据库查询: 2018-07-16 17:56:25.054 DEBUG 17312 --- [nio-9010-exec-3] c.s.h.m.C.selectSelective : ==> Preparing: select id, card_number, customer_id, customer_nam......

kuchawyz
今天
0
0
译:Self-Modifying cod 和cacheflush

date: 2014-11-26 09:53 翻译自: http://community.arm.com/groups/processors/blog/2010/02/17/caches-and-self-modifying-code Cache处在CPU核心与内存存储器之间,它给我们的感觉是,它具......

我叫半桶水
今天
0
0
Artificial Intelligence Yourself

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理。Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算,TensorFlow为张量从流...

孟飞阳
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部