Spring Security默認的行為是每個登錄成功的用戶會新建一個Session。這也就是下面的配置的效果:
<http create-session="ifRequired">...</http>
這貌似沒有問題,但其實對大規模的網站是致命的。用戶越多,新建的session越多,最后的結果是JVM內存耗盡,你的web服務器徹底掛了。有session的另外一個嚴重的問題是scalability能力,用戶壓力上來了不能馬上新建一台Jetty/Tomcat服務器,因為要考慮Session同步的問題。 先來看看Session過多導致的Jetty JVM 內存耗盡:
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。開銷顯然是增大了,因為每次請求都必須在服務器端重新認證並建立用戶角色和權限的上下文。
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等,具體參考這個網頁。
參考:https://www.cnblogs.com/Mainz/p/3230077.html

