1. 程式人生 > >Spring Security 梳理 - session

Spring Security 梳理 - session

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

也就是說每個請求都是無狀態的獨立的,需要被再次認證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