使用 Spring Security 进行登陆权限验证,RESTful 的 URL 想同时支持 Web 和 移动端访问:
Web 访问:使用 cookie + session
移动端访问: 只使用 token,不要产生 session
例如访问 http://localhost:8080/api/json 这个 URL 时
Web 访问:如果用户没有登陆则调到登陆页面然用户登陆,使用的是默认的 UsernamePasswordAuthenticationToken 进行登陆验证,这时会创建 cookie 和 session,每次访问都会使用同一个 session
移动端访问: 事先获取一个 token,然后在请求中带上 token,使用自定义的 AppAuthenticationToken 进行登陆验证,这个时候不希望创建 session (doFilter 里也会调用下一个 filter UsernamePasswordAuthenticationToken,但是因为验证通过了,在 UsernamePasswordAuthenticationToken 不会再进行登陆验证了)
下面的实现 Web 端多次访问都会使用同一个 session (因为 cookie 里有 session id),token 访问也能够正常处理返回数据,但是 token 的方式每次访问都会生成一个新的 session,造成大量的内存浪费,有没有什么办法当在使用 token 访问时不让 Spring Security 创建 session?
package com.xtuer.security;
import com.xtuer.bean.User;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AppAuthenticationToken extends AbstractAuthenticationProcessingFilter {
public AppAuthenticationToken() {
super(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
User user = new User("admin", "", "ROLE_USER");
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
Authentication authResult = null;
if (request.getHeader("auth-token") != null) {
// 问题: 有没有什么办法在这里标记一下,让 Spring Security 不要生成 session?
// 其他办法也可以
authResult = attemptAuthentication(request, response);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
chain.doFilter(request, response);
}
}
<beans:bean id="authenticationFilter" class="com.xtuer.security.AppAuthenticationToken">
<beans:property name="authenticationManager" ref="authenticationManager"/>
</beans:bean>
<http security="none" pattern="/page/login"/>
<http security="none" pattern="/static/**"/>
<http auto-config="true">
<intercept-url pattern="/page/admin" access="hasRole('ROLE_ADMIN')"/>
<intercept-url pattern="/api/json" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')"/>
<form-login login-page="/page/login"
login-processing-url="/login"
default-target-url ="/"
authentication-failure-url="/page/login?error=1"
username-parameter="username"
password-parameter="password"/>
<logout logout-url="/logout" logout-success-url="/page/login?logout=1"/>
<access-denied-handler error-page="/page/deny"/>
<csrf disabled="true"/>
<custom-filter ref="authenticationFilter" before="FORM_LOGIN_FILTER"/>
</http>
如果是存 RESTful 的应用,那么可以设置 http 的 create-session="stateless" 不让创建 session,但是希望的是普通应用里使用 RESTful 的 API 提供服务。
如果 API 要求登陆,可以不通过 Spring Security 进行验证,有个比较蹩脚的办法,在 spring mvc interceptor 里进行判断是否登陆过了或者 token 是否有效,不过不够理想,因为这里不能进行权限校验。