【Spring Security】六、自定义认证处理的过滤器

这里接着上一章的自定义过滤器,这里主要的是配置自定义认证处理的过滤器,并加入到FilterChain的过程。在我们自己不在xml做特殊的配置情况下,security默认的做认证处理的过滤器为UsernamePasswordAuthenticationFilter,通过查看源码知道,做认证处理的方法为attemptAuthentication,这个方法的主要作用就是将用户输入的账号和密码,封装成一个UsernamePasswordAuthenticationToken对象,然后通过setDetails方法将这个对象储存起来,然后调用this.getAuthenticationManager().authenticate(authRequest)方法返回一个Authentication对象。其中这个过程this.getAuthenticationManager().authenticate(authRequest)又调用的其他的许多类,这里简单的讲解下:

UsernamePasswordAuthenticationFilter-->ProviderManager-->AbstractUserDetailsAuthenticationProvider-->DaoAuthenticationProvider-->JdbcDaoImpl

 

  • 当输入用户名和密码后,点击登陆到达UsernamePasswordAuthenticationFilterattemptAuthentication方法,这个方法是登陆的入口
  • 然后其调用ProviderManager中的authenticate方法,而ProviderManager委托给AbstractUserDetailsAuthenticationProviderauthenticate
  • 然后AbstractUserDetailsAuthenticationProvider调用DaoAuthenticationProvider中的retrieveUser
  • DaoAuthenticationProvider类的retrieveUser方法中,因为要通过输入的用户名获取到一个UserDetails,所以其调用JdbcDaoImpl中的loadUserByUsername方法,该方法给它的调用者返回一个查询到的用户(UserDetails),最终AbstractUserDetailsAuthenticationProviderauthenticate方法中会得到一个UserDetails对象user
  • 然后接着执行preAuthenticationChecks.check(user)additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);其中前面这个方法是判断,查询的用户是否可用或被锁等,后面的则是判断查询到的user对象的密码是否和authentication(这个对象其实就是存储用户输入的用户名和密码)的密码一样,若一样则表示登陆成功,若错误,则throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);Bad credentials这个消息就是登陆失败后的信息。

初步的讲解了登陆过程中类的调用,那么下面这个例子就是自定义一个MyUsernamePasswordAuthenticationFilter来代替默认的  UsernamePasswordAuthenticationFilter

一、自定义MyUsernamePasswordAuthenticationFilter

这个类可以继承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中去,实际上这个方法中,一般会直接验证用户输入的和通过用户名从数据库里面查到的用户的密码是否一致,如果不一致,就抛异常,否则继续向下执行。

二、配置MyUsernamePasswordAuthenticationFilter并将其加入到FilterChain中去

  • MyUsernamePasswordAuthenticationFilterfilterProcessesUrl属性为登陆的过滤的地址
  • authenticationManagerauthentication-manager标签中配置的东西
  • authenticationSuccessHandler为验证成功后跳转的处理器
  • authenticationFailureHandler为验证失败的处理器
  • 另外还要配置一个出登陆引导的处bean:LoginUrlAuthenticationEntryPoint
配置代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security.xsd">
    <!-- 不需要访问权限 -->
    <http pattern="/page/login.jsp" security="none"></http>
    <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <!-- <form-login login-page="/page/login.jsp" default-target-url="/page/admin.jsp" authentication-failure-url="/page/login.jsp?error=true" /> -->
        <logout invalidate-session="true" logout-success-url="/page/login.jsp" logout-url="/j_spring_security_logout" />
        <!-- 自定义登录过滤器 -->
        <custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" />
        <!-- 通过配置custom-filter来增加过滤器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。 -->
        <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
    </http>
    <!-- 登录切入点 -->
    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/page/login.jsp"/>
    </beans:bean>
    <!-- 自定义登录过滤器 -->
    <beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.sunny.auth.MyUsernamePasswordAuthenticationFilter">
        <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler" />
        <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler" />
    </beans:bean>
    <!-- 登录成功 -->
    <beans:bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="targetUrlParameter" value="/page/admin.jsp" />
    </beans:bean>
     <!-- 登录失败 -->
    <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/page/login.jsp" />
    </beans:bean> 
    
    <!-- 认证过滤器 -->
    <beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <!-- 用户拥有的权限 -->
        <beans:property name="accessDecisionManager" ref="accessDecisionManager" />
        <!-- 用户是否拥有所请求资源的权限 -->
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <!-- 资源与权限对应关系 -->
        <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
    </beans:bean>
    
    <!-- 授权管理器 -->
    <beans:bean id="accessDecisionManager" class="com.sunny.auth.MyAccessDecisionManager">
    </beans:bean>
    
     <!--自定义的切入点-->
    <beans:bean id="securityMetadataSource" class="com.sunny.auth.MyFilterInvocationSecurityMetadataSource">
        <beans:property name="builder" ref="builder"/>
    </beans:bean>
    
    <beans:bean id="builder" class="com.sunny.auth.JdbcRequestMapBulider"> 
        <beans:property name="dataSource" ref="dataSource" /> 
        <beans:property name="resourceQuery" value="select re.res_string,r.name from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id" /> 
    </beans:bean>
    
     <!--认证管理-->
    <authentication-manager alias="authenticationManager">
        <authentication-provider>
            <jdbc-user-service data-source-ref="dataSource"
                users-by-username-query="select username,password,status as enabled from user where username = ?"
                authorities-by-username-query="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" />
        </authentication-provider>
    </authentication-manager>
    
</beans:beans>

 

其他配置与前面章节一样

三、验证

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改为自定义的类

  <beans:bean id="loginLogAuthenticationSuccessHandler" class="com.sunny.auth.CustomLoginSuccessHandler">
        <beans:property name="targetUrlParameter" value="/page/admin.jsp" />
    </beans:bean>

然后重新启动下,验证OK

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值