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:HttpSessionSecurityContextRepositorySessionManagementFilterRequestCacheFilter.

也就是说每个请求都是无状态的独立的,需要被再次认证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:

关于Spring Security中无Session和无状态stateless的更多相关文章

  1. 六:Spring Security 中使用 JWT

    Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...

  2. Spring Security 中的过滤器

    本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. Spring Security 的本质是一个过滤器链(filter chain) ...

  3. spring security中动态更新用户的权限

    在程序的执行过程中,有时有这么一种需求,需要动态的更新某些角色的权限或某些人对应的权限,当前在线的用户拥有这个角色或拥有这个权限时,在不退出系统的情况下,需要动态的改变的他所拥有的权限. 需求:张三 ...

  4. [收藏]Spring Security中的ACL

    ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...

  5. Spring MVC 中获取session的几种方法

    Spring MVC 中使用session是一种常见的操作,但是大家上网搜索一下可以看到获取session的方式方法五花八门,最近,自己终结了一下,将获取session的方法记录下来,以便大家共同学习 ...

  6. Spring Security中html页面设置hasRole无效的问题

    Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...

  7. Spring Security 中的 Bcrypt

    最近在写用户管理相关的微服务,其中比较重要的问题是如何保存用户的密码,加盐哈希是一种常见的做法.知乎上有个问题大家可以先读一下: 加盐密码保存的最通用方法是? 对于每个用户的密码,都应该使用独一无二的 ...

  8. 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配

    浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...

  9. 看源码,重新审视Spring Security中的角色(roles)是怎么回事

    在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...

  10. 五:Spring Security 中的角色继承问题

    Spring Security 中的角色继承问题 以前的写法 现在的写法 源码分析 SpringSecurity 在角色继承上有两种不同的写法,在 Spring Boot2.0.8(对应 Spring ...

随机推荐

  1. java IODemo

    关键代码:         RandomAccessFile file = new RandomAccessFile("temp.dat", "rw"); fi ...

  2. event based xml parser (SAX) demo

    import java.io.ByteArrayInputStream; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SA ...

  3. 【转】Javascript基本类型和引用类型的区别

    根据[转贴]进一步补充 今天明白了一个困扰很久的问题:引用类型和基本类型的区别与联系要明白这个问题,首先需要理解堆栈的概念.那什么又是堆栈,有什么区别和联系呢?堆:首先堆是动态分配的,JVM并不会自动 ...

  4. 关于分页插件PageHelper

    上课的时候学习了分页插件,感受到了它的强大,这里总结如下: 1.首先在spring配置文件中引入依赖jar包: <dependency> <groupId>com.github ...

  5. SRM464

    250pt       对于一个字符串,当他为colorful时满足其所有的子串的值不一样, 值的定义如下,如“236”,定义其值为2 * 3 * 6 = 36. 现题目给定字符串长度n(1 < ...

  6. Gitlab 赋予某台机器git clone的权限 Deploy key

    开发项目CI(持续化部署)的时候,需要赋予jeckins所在的机器从gitlab远程仓库克隆代码到本地的权限. 之前我们基本都是通过管理gitlab某个项目的成员的方式,管理gitlab的权限. 但是 ...

  7. Android-Java控制多线程执行顺序

    功能需求: Thread-0线程:打印 1 2 3 4 5 6 Thread-1线程:打印1 1 2 3 4 5 6 先看一个为实现(功能需求的案例) package android.java; // ...

  8. 用指定的用户名和密码无法登录到该ftp服务器

    今天在win2008 R2 服务器上默认部署FTP站点时遇到了两个小问题,在网上找了好久资料后发现还是解决不了问题,最终找到问题的原因,在此共享给大家 1.Windows无法访问此文件夹.请确保输入的 ...

  9. C# 动态创建数据库三(MySQL)

    前面有说明使用EF动态新建数据库与表,数据库使用的是SQL SERVER2008的,在使用MYSQL的时候还是有所不同 一.添加 EntityFramework.dll ,System.Data.En ...

  10. UWP 2018 新版 NavigationView 尝鲜

    本文参考了官方文档以及提供的示例代码(官方代码貌似有点误导,所以写了这一篇,并且文末有GayHub代码地址) 官方文档发布于20180806,说明NavigationView刚发布了没几天,还在开发中 ...