这里接着上一章的自定义过滤器,这里主要的是配置自定义认证处理的过滤器,并加入到FilterChain的过程。在我们自己不在xml做特殊的配置情况下,security默认的做认证处理的过滤器为UsernamePasswordAuthenticationFilter,通过查看源码知道,做认证处理的方法为attemptAuthentication,这个方法的主要作用就是将用户输入的账号和密码,封装成一个UsernamePasswordAuthenticationToken对象,然后通过setDetails方法将这个对象储存起来,然后调用this.getAuthenticationManager().authenticate(authRequest)方法返回一个Authentication对象。其中这个过程this.getAuthenticationManager().authenticate(authRequest)又调用的其他的许多类,这里简单的讲解下:
UsernamePasswordAuthenticationFilter-->ProviderManager-->AbstractUserDetailsAuthenticationProvider-->DaoAuthenticationProvider-->JdbcDaoImpl
初步的讲解了登陆过程中类的调用,那么下面这个例子就是自定义一个MyUsernamePasswordAuthenticationFilter来代替默认的 UsernamePasswordAuthenticationFilter。
这个类可以继承UsernamePasswordAuthenticationFilter,然后重写attemptAuthentication方法,这个方法是登陆的入口方法。
package com.sunny.auth;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.authentication.UsernamePasswordAuthenticationFilter;
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
public static final String USERNAME = "j_username";
public static final String PASSWORD = "j_password";
/**
* 用户登录验证方法入口
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
// 加密密码(根据“密码{用户名})进行加密
// String sh1Password = password + "{" + username + "}";
// PasswordEncoder passwordEncoder = new StandardPasswordEncoderForSha1();
// String result = passwordEncoder.encode(sh1Password);
// UserInfo userDetails = (UserInfo)
// userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 获取用户名
*/
@Override
protected String obtainUsername(HttpServletRequest request) {
Object obj = request.getParameter(USERNAME);
return null == obj ? "" : obj.toString().trim().toLowerCase();
}
/**
* 获取密码
*/
@Override
protected String obtainPassword(HttpServletRequest request) {
Object obj = request.getParameter(PASSWORD);
return null == obj ? "" : obj.toString();
}
}
上述的代码这样写其实和默认的UsernamePasswordAuthenticationFilter并没有什么区别,但是这里主要是学会将自定义的Filter加入到security中的FilterChain中去,实际上这个方法中,一般会直接验证用户输入的和通过用户名从数据库里面查到的用户的密码是否一致,如果不一致,就抛异常,否则继续向下执行。
配置代码如下:
其他配置与前面章节一样
one one no think!本该圆满结局却剧情反转!
调试后发现,spring security的源码在处理成功跳转的过程中竟然没有把跳转页面的地址处理好,所以点击登录后的请求地址变成了默认的“/”根目录,而web.xml配置的默认访问页面是login.jsp
不知道源码编写者是怎么想的
问题出在AbstractAuthenticationTargetUrlRequestHandler.class这个类的determineTargetUrl方法里
这段代码中targetUrlParameter本来已经拿到值了,但是经过判断后没有直接给targetUrl,却从request里把页面跳转的路径当作了request的参数来获取value,头大大的
修改方式一:
将成功跳转的属性名称targetUrlParameter改为defaultTargetUrl
修改方式二:
自己实现AuthenticationSuccessHandler接口,或者继承SimpleUrlAuthenticationSuccessHandler类并重写方法
我采用第一种,增加一个SavedRequestAwareAuthenticationSuccessHandler类
package com.sunny.auth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* 自定义登录成功后的处理
* @ClassName: SparkAuthenticationSuccessHandler
* @Description: TODO(这里用一句话描述这个类的作用)
* @author Sunny
* @date 2018年7月6日 上午9:20:56
*
*/
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler{
Log logger = LogFactory.getLog(CustomLoginSuccessHandler.class);
private String targetUrlParameter;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.info("登录成功,即将forward:" + this.targetUrlParameter);
// 登录成功之后将SECURITY放入上下文中
request.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
request.getRequestDispatcher(this.targetUrlParameter).forward(request, response);
}
public String getTargetUrlParameter() {
return targetUrlParameter;
}
public void setTargetUrlParameter(String targetUrlParameter) {
this.targetUrlParameter = targetUrlParameter;
}
}
然后把成功跳转的bean改为自定义的类
然后重新启动下,验证OK