设为首页 收藏本站
查看: 1284|回复: 0

[经验分享] 关于Spring Security中无Session和无状态stateless

[复制链接]

尚未签到

发表于 2017-2-28 12:12:01 | 显示全部楼层 |阅读模式
  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打开看看:
DSC0000.png

  Heap dump分析说Web服务器使用了大量的ConcurrentHashMaps来存储Session:
   DSC0001.png
  OK。既然Session导致了访问量大了内存溢出,解决办法就是Spring Security禁用Session:



<http create-session="never">
<!-- ... -->
</http>
  注:这里的意思是说Spring Security对登录成功的用户不会创建Session了,但你的application还新建了session,那么Spring Security会用它的。这点注意了。
  禁用了以后用VisualVM看看效果:
DSC0002.png

  效果非常好。而且没有多台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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348445-1-1.html 上篇帖子: maven的生命周期和插件 下篇帖子: elasticsearch 口水篇(1) 安装、插件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表