关于Spring Security中无Session和无状态stateless
Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, RMI, JAAS, JOSSO, OPENNMS, GRAIS....关于其详尽的说明,请参考Spring Security官方网页。但它默认的表table比较少,默认只有用户和角色,还有一个group,不能满足RBAC的最高要求,通过扩展User - Role - Resource - Authority,我们可以实现用户,角色,资源,权限和相互关系的7张表,实现对权限的最灵活配置。
常见的Spring Security配置:
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source>
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>
可见只有基本的ROLE什么的(Spring Security仅有的几个表),不能实现对资源和权限的灵活配置。在xml配置中也没有在数据库中灵活。我们可以通过扩展User - Role - Resource - Authority,我们可以实现用户,角色,资源,权限和相互关系的7张表,实现对权限的最灵活配置。
具体扩展Spring Security实现用户,角色,资源,权限和相互关系的7张表的方法我会在另外的blog中说明。最后能实现用户,角色,资源,权限都可以在数据库表里灵活配置,在后台java的过滤器和拦截器可以自动对资源的访问进行拦截和授权。在html前台,也可以通过扩展Spring Security Tag来实现在html里面用tag来配置某html局部的访问授权。关于如何实现扩展Spring Security Tag来实现在html里面用tag来配置某html局部的访问授权, 我会在另外的blog中说明。今天主要讲Spring Security如何配置成没有Session和无状态session?
Spring Secrity 的 create-session
=
"never"
Spring Security默认的行为是每个登录成功的用户会新建一个Session。这也就是下面的配置的效果:
<http create-session="ifRequired">...</http>
这貌似没有问题,但其实对大规模的网站是致命的。用户越多,新建的session越多,最后的结果是JVM内存耗尽,你的web服务器彻底挂了。有session的另外一个严重的问题是scalability能力,用户压力上来了不能马上新建一台Jetty/Tomcat服务器,因为要考虑Session同步的问题。 先来看看Session过多导致的Jetty JVM 内存耗尽:
java.lang.OutOfMemoryError: Java heap space
(注:Tomcat启动脚本必须加上: -XX:+HeapDumpOnOutOfMemoryError
才能获得 Java heap dump)
用VisualVM打开看看:
Heap dump分析说Web服务器使用了大量的ConcurrentHashMaps来存储Session:
OK。既然Session导致了访问量大了内存溢出,解决办法就是Spring Security禁用Session:
<http create-session="never">
<!-- ... -->
</http>
注:这里的意思是说Spring Security对登录成功的用户不会创建Session了,但你的application还新建了session,那么Spring Security会用它的。这点注意了。
禁用了以后用VisualVM看看效果:
效果非常好。而且没有多台web服务器的session同步和共享的问题,可以很方便的搭建多台web应用服务器的集群,前面加上Nginx反向代理和负载均衡。
Spring security 3.1的 create-session="stateless"
Spring Security 3.1开始支持stateless authentication(具体查看 What‘s new in Spring Security 3.1?),配置方法是:
<http create-session="stateless">
<!-- ... -->
</http>
主要是在RESTful API,无状态的web调用的stateless authentication。
这个配置的意思是:Spring Security对登录成功的用户不会创建Session了,你的application也不会允许新建session,而且Spring Security会跳过所有的 filter chain:HttpSessionSecurityContextRepository, SessionManagementFilter, RequestCacheFilter.
也就是说每个请求都是无状态的独立的,需要被再次认证re-authentication。开销显然是增大了,因为每次请求都必须在服务器端重新认证并建立用户角色和权限的上下文。
大家知道,Spring Security在认证的过程中,Spring Security会运行一个过滤器(SecurityContextPersistenceFilter)来存储请求的Security Context,这个上下文的存储是一个策略模式,但默认的是保存在HTTP Session中的HttpSessionSecurityContextRepository。现在我们设置了 create-session=”stateless”,就会保存在NullSecurityContextRepository,里面没有任何session在上下文中保持。既然没有为何还要调用这个空的filter?因为需要调用这个filter来保证每次请求完了SecurityContextHolder被清空了,下一次请求必须re-authentication。
Stateless的RESTful authentication认证
刚才说了,配置为stateless的使用场景,例如RESTful api,其每个请求都是无状态的独立的,需要被再次认证re-authentication。操作层面,具体做法是:在每一个REST的call的头header(例如:@HeaderParam annotation. 例子: @HeaderParam.)都带user token 和 application ID,然后在服务器端对每一请求进行re-authentication. (注意:把token放在uri中是糟糕的做法,首先是安全的原因,其次是cache的原因,尽量放在head中)可以写一个拦截器来实现:
@Provider
@ServerInterceptor
public class RestSecurityInterceptor implements PreProcessInterceptor { @Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
throws UnauthorizedException { String token = request.getHttpHeaders().getRequestHeader("token").get(0); // user not logged-in?
if (checkLoggedIn(token)) {
ServerResponse response = new ServerResponse();
response.setStatus(HttpResponseCodes.SC_UNAUTHORIZED);
MultivaluedMap<String, Object> headers = new Headers<Object>();
headers.add("Content-Type", "text/plain");
response.setMetadata(headers);
response.setEntity("Error 401 Unauthorized: "
+ request.getPreprocessedPath());
return response;
}
return null;
}
}
Spring Security配置文件:
<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
<security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<security:intercept-url pattern="/authenticate" access="permitAll"/>
<security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http> <bean id="CustomAuthenticationEntryPoint"
class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" /> <bean class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter"
id="authenticationTokenProcessingFilter">
<constructor-arg ref="authenticationManager" />
</bean>
CustomAuthenticationEntryPoint:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
}
}
AuthenticationTokenProcessingFilter:
@Autowired UserService userService;
@Autowired TokenUtils tokenUtils;
AuthenticationManager authManager; public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
this.authManager = authManager;
} @Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
@SuppressWarnings("unchecked")
Map<String, String[]> parms = request.getParameterMap(); if(parms.containsKey("token")) {
String token = parms.get("token")[0]; // grab the first "token" parameter // validate the token
if (tokenUtils.validate(token)) {
// determine the user based on the (already validated) token
UserDetails userDetails = tokenUtils.getUserFromToken(token);
// build an Authentication object with the user's info
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
// set the authentication into the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));
}
}
// continue thru the filter chain
chain.doFilter(request, response);
}
}
TokenUtils:
public interface TokenUtils {
String getToken(UserDetails userDetails);
String getToken(UserDetails userDetails, Long expiration);
boolean validate(String token);
UserDetails getUserFromToken(String token);
}
Spring Security其它方面
其他的比如concurrent Session,意思是同一个用户允许同时在线(不同地点)的数量。还有Session劫持的防止, auto-remember等,具体参考这个网页。
reference pages:
- Session,有没有必要使用它?(Fisher Li)
- Spring Security集成SSO单点登录
- Spring Security 3.1.x API document
- Wiki: Digest Access Authentication
- Wiki: Basic Access Authentication
- Amazon S3: Authenticating REST Request
- Stackoverflow: RESTful authentication
- Best Practices for securing a REST API / web service
- Stackoverflow: RESTful Authentication via Spring
关于Spring Security中无Session和无状态stateless的更多相关文章
- 六:Spring Security 中使用 JWT
Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...
- Spring Security 中的过滤器
本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. Spring Security 的本质是一个过滤器链(filter chain) ...
- spring security中动态更新用户的权限
在程序的执行过程中,有时有这么一种需求,需要动态的更新某些角色的权限或某些人对应的权限,当前在线的用户拥有这个角色或拥有这个权限时,在不退出系统的情况下,需要动态的改变的他所拥有的权限. 需求:张三 ...
- [收藏]Spring Security中的ACL
ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...
- Spring MVC 中获取session的几种方法
Spring MVC 中使用session是一种常见的操作,但是大家上网搜索一下可以看到获取session的方式方法五花八门,最近,自己终结了一下,将获取session的方法记录下来,以便大家共同学习 ...
- Spring Security中html页面设置hasRole无效的问题
Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...
- Spring Security 中的 Bcrypt
最近在写用户管理相关的微服务,其中比较重要的问题是如何保存用户的密码,加盐哈希是一种常见的做法.知乎上有个问题大家可以先读一下: 加盐密码保存的最通用方法是? 对于每个用户的密码,都应该使用独一无二的 ...
- 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配
浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...
- 看源码,重新审视Spring Security中的角色(roles)是怎么回事
在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...
- 五:Spring Security 中的角色继承问题
Spring Security 中的角色继承问题 以前的写法 现在的写法 源码分析 SpringSecurity 在角色继承上有两种不同的写法,在 Spring Boot2.0.8(对应 Spring ...
随机推荐
- stm32常识
cmsis全称Cortex Microcontroller Software Interface Standard,就是Cortex微处理器软件接口标准 stm32每组gpio有7组端口,分别是2个3 ...
- io类型
非阻塞io from socket import * import time s=socket(AF_INET,SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.li ...
- springMvc里的mvc:resources与静态资源的访问
在进行Spring MVC的配置时,通常我们会配置一个dispatcher servlet用于处理对应的URL.配置如下: <servlet> <servlet-name&g ...
- js-数组方法push
<script type="text/javascript"> var arr=[1,2,3,4,5] arr.push(6,7) ...
- 必修3第三章概率mindmaps
% !Mode:: "TeX:UTF-8" \documentclass{article} \usepackage[screen]{geometry} \usepackage[no ...
- PB函数大全【转自 http://blog.csdn.net/xiaoxian8023 】
Abs()功能计算绝对值.语法Abs ( n )参数n:要得到绝对值的数值型变量或表达式返回值返回值的数据类型与n的数据类型相同,函数执行成功时返回n的绝对值.如果参数n的值为NULL,Abs()函数 ...
- 使用c# 实现冒泡排序
冒泡排序是一个经典的案例 实现原理就数与数前后两两比较,如果前面比后面大则交换位置.最终达到从小到大的顺序,这样的排序方式就是冒泡排序. //冒泡排序 ;//定义一个中间变量,用来交换值 , , , ...
- 【转】TOP10美国虚拟主机/网站空间推荐
原文:http://www.laozuo.org 不同的站长用户需要不同的主机产品,并不是所有的站长, 所有的网站都想放置在VPS服务器中的.虚拟主机也有虚拟主机的方便和优势,下面为老左精选的10个比 ...
- 【BZOJ2328】 [HNOI2011]赛车游戏
BZOJ2328 [HNOI2011]赛车游戏 前言 这道题目我真的佛了,卡精度+卡时间这就是下一个聊天鬼才. Solution 首先可以二分出最大速度,然后考虑下坡的话可能有更好的解,然后这样子算一 ...
- Spring boot中使用log4j
我们知道,Spring Boot中默认日志工具为logback,但是对于习惯了log4j的开发者,Spring Boot依然可以很好的支持,只是需要做一些小小的配置功能.Spring Boot使用lo ...