有霾走遍天下,无霾寸步难行
最近在用spring boot 注解方式 实现 rest 风格的 angular 2 API, 有坑,虽记录之,如下:
Spring Security
是spring出的一套集认证与授权一体的库。官方文档大部分都是以网页提交表单的方式说明的。
Spring Security 认证源码
- Spring Security 4 认证代码在
UsernamePasswordAuthenticationFilter
过滤器中,此过滤器继承AbstractAuthenticationProcessingFilter
,UsernamePasswordAuthenticationFilter
过滤器官方源码:
package org.springframework.security.web.authentication;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
/* 用户名 举个使用栗子:<form><input name="username"></form> */
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
/* 密码 举个使用栗子:<form><input name="password"></form> */
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
/* 登录只允许POST提交 */
private boolean postOnly = true;
/* 添加 /login 登录访问 */
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
/* 认证用户帐号和密码 */
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
- 如果需要自定义认证(本例子不需要重写),需重写
UsernamePasswordAuthenticationFilter
过滤器,
并在WebSecurityConfiguration.configure(HttpSecurity http){}
方法中添加此过滤器。源码往下看
解决跨域问题,一个CSRF和另一个CORS
- CSRF(Cross-Site Request Forgery,跨站点伪造请求)
- CORS (Cross Origin Resourse-Sharing, 跨域资源共享)
.csrf().disable()
关闭CSRF.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
允许跨域请求资源。.httpBasic()
也是要添加的。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic()
.and()
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
.addFilterBefore(new MyUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/cnvd/list/1").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.successHandler(new MyAuthenticationSuccessHandler())
.failureHandler(new MyAuthenticationFailureHandler())
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
CORSFilter
过滤器
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CORSFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
认证之后返回JSON数据,而非重定向(Redirect)
- 实现
AuthenticationSuccessHandler
和AuthenticationFailureHandler
- 在
WebSecurityConfiguration.configure(HttpSecurity http){}
方法中启用。参考:WebSecurityConfiguration
类
AuthenticationSuccessHandler
源码
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
PrintWriter writer;
String returnStr = "{\"message\":\"sucess\"}";
response.setStatus(200);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
requestCache.removeRequest(request, response);
clearAuthenticationAttributes(request);
}
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}
AuthenticationFailureHandler
源码
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
PrintWriter writer;
String returnStr = "{exception:{name:'" + exception.getClass()
+ "',message:'" + exception.getMessage() + "'}}";
System.out.println(this.getClass().toString()+":"+returnStr);
writer = response.getWriter();
writer.write(returnStr);
writer.flush();
writer.close();
}
}
Spring Security 授权
稍后更新
这里有个结语
CORSFilter
有个注解的写法corsConfigurer()
函数相关,但是对Spring Security默认的 /login 无效,其他自定义Controller请求可以。- 有关CSRF和CORS可以参考:http://www.cnblogs.com/lailailai/p/4528092.html