这里接着上一章的自定义过滤器,这里主要的是配置自定义认证处理的过滤器,并加入到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

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

【Spring Security】六、自定义认证处理的过滤器的更多相关文章

  1. spring security 3 自定义认证,授权示例

    1,建一个web project,并导入所有需要的lib. 2,配置web.xml,使用Spring的机制装载: <?xml version="1.0" encoding=& ...

  2. Spring Security OAuth2.0认证授权六:前后端分离下的登录授权

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  3. [权限管理系统(四)]-spring boot +spring security短信认证+redis整合

    [权限管理系统]spring boot +spring security短信认证+redis整合   现在主流的登录方式主要有 3 种:账号密码登录.短信验证码登录和第三方授权登录,前面一节Sprin ...

  4. 认证与授权】Spring Security系列之认证流程解析

    上面我们一起开始了Spring Security的初体验,并通过简单的配置甚至零配置就可以完成一个简单的认证流程.可能我们都有很大的疑惑,这中间到底发生了什么,为什么简单的配置就可以完成一个认证流程啊 ...

  5. Spring Security OAuth2.0认证授权四:分布式系统认证授权

    Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...

  6. spring security 表单认证的流程

    spring security表单认证过程 表单认证过程 Spring security的表单认证过程是由org.springframework.security.web.authentication ...

  7. Spring Security 解析(二) —— 认证过程

    Spring Security 解析(二) -- 认证过程   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...

  8. Spring Security OAuth2.0认证授权三:使用JWT令牌

    Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...

  9. Springboot WebFlux集成Spring Security实现JWT认证

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 在之前的文章<Springboot集成Spring Security实现JWT认证>讲解了如何在传统 ...

随机推荐

  1. NetSpeed

    NetSpeed公司提供的NOC包括三部分,可以通过NocStudio进行配置生成. 1)NetSpeed Orion,面向快速SoC design的可综合平台. 2)Linley NetSpeed ...

  2. EasyUI表格DataGrid前端分页和后端分页的总结

    Demo简介 Demo使用Java.Servlet为后台代码(数据库已添加数据),前端使用EasyUI框架,后台直接返回JSON数据给页面 1.配置Web.xml文件 <?xml version ...

  3. Python大神成长之路: 第三次学习记录 集合 函数 装饰 re

    学习记录day03   字符串可以直接切片,But字符串不可修改 字符串修改:生成了一个新的字符串 LIst修改,在原基础上修改(原内存上)     集合是一个无序的,不重复的数据组合,它的主要作用如 ...

  4. js三目学习

    <script> var n=1; n>1?document.write('大于1哦'):document.write('小于或等于1哦') //n=n>1?document. ...

  5. xshell的一些基本操作

    挺全面的一篇文章,没事可以看看. (1)命令ls——列出文件  ls -la 给出当前目录下所有文件的一个长列表,包括以句点开头的“隐藏”文件  ls a* 列出当前目录下以字母a开头的所有文件  l ...

  6. ubuntu16.04——WingIDE安装 操作服务器是一件很好玩的事情

    1.在服务器上部署环境时,区分linux 系统和winddos系统 2.下载安装包: 3.输入命令操作 4.进入相对应的目录下: 5.命令 6.发生错误,更新环境 7.安装成功

  7. linux下怎么删除名称带空格的文件

    linux下怎么删除名称带空格的文件-rm 'mysql bin.000005' 用引号把文件名括起来 某些情况下会出现名称带空格的文件, 如果想要删除的话,直接用rm mysql bin.00000 ...

  8. How to install john deere service advisor 4.2.005 on win 10 64bit

    How to install john deere service advisor 4.2.005 with the February 2016 data base disks on a machin ...

  9. How to use CAR FANS C800 Diagnostic Scan Tool to do diagnosis operation

    How to use Heavy Duty Diagnostic CAR FANS C800 Diagnostic Scan Tool to do diagnosis operation Here i ...

  10. 如何用nginx在本地把9000端口转发到80端口上

    起因看到一个用java写的轻博客,于是就兴致冲冲的试用一下.由于是lnmp的环境,Nginx占用了80端口,新博客只能用其他的端口,这里选择了9000端口,本地测试没问题.总不能访问了域名然后在加上端 ...