zoukankan      html  css  js  c++  java
  • Spring Security笔记:自定义Login/Logout Filter、AuthenticationProvider、AuthenticationToken

    前面的学习中,配置文件中的<http>...</http>都是采用的auto-config="true"这种自动配置模式,根据Spring Security文档的说明:

    ------------------

    auto-config Automatically registers a login form, BASIC authentication, logout services. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element).

    ------------------

    可以理解为:

    1     <http>
    2         <form-login />
    3         <http-basic />
    4         <logout />
    5     </http>

    下面是Spring Security Filter Chain的列表:

    Table 1. Standard Filter Aliases and Ordering
    AliasFilter ClassNamespace Element or Attribute

    CHANNEL_FILTER

    ChannelProcessingFilter

    http/intercept-url@requires-channel

    SECURITY_CONTEXT_FILTER

    SecurityContextPersistenceFilter

    http

    CONCURRENT_SESSION_FILTER

    ConcurrentSessionFilter

    session-management/concurrency-control

    HEADERS_FILTER

    HeaderWriterFilter

    http/headers

    CSRF_FILTER

    CsrfFilter

    http/csrf

    LOGOUT_FILTER

    LogoutFilter

    http/logout

    X509_FILTER

    X509AuthenticationFilter

    http/x509

    PRE_AUTH_FILTER

    AstractPreAuthenticatedProcessingFilter Subclasses

    N/A

    CAS_FILTER

    CasAuthenticationFilter

    N/A

    FORM_LOGIN_FILTER

    UsernamePasswordAuthenticationFilter

    http/form-login

    BASIC_AUTH_FILTER

    BasicAuthenticationFilter

    http/http-basic

    SERVLET_API_SUPPORT_FILTER

    SecurityContextHolderAwareRequestFilter

    http/@servlet-api-provision

    JAAS_API_SUPPORT_FILTER

    JaasApiIntegrationFilter

    http/@jaas-api-provision

    REMEMBER_ME_FILTER

    RememberMeAuthenticationFilter

    http/remember-me

    ANONYMOUS_FILTER

    AnonymousAuthenticationFilter

    http/anonymous

    SESSION_MANAGEMENT_FILTER

    SessionManagementFilter

    session-management

    EXCEPTION_TRANSLATION_FILTER

    ExceptionTranslationFilter

    http

    FILTER_SECURITY_INTERCEPTOR

    FilterSecurityInterceptor

    http

    SWITCH_USER_FILTER

    SwitchUserFilter

    N/A

    其中红色标出的二个Filter对应的是 “注销、登录”,如果不使用auto-config=true,开发人员可以自行“重写”这二个Filter来达到类似的目的,比如:默认情况下,登录表单必须使用post方式提交,在一些安全性相对不那么高的场景中(比如:企业内网应用),如果希望通过类似 http://xxx/login?username=abc&password=123的方式直接登录,可以参考下面的代码:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import javax.servlet.http.HttpServletRequest;
     4 import javax.servlet.http.HttpServletResponse;
     5 
     6 //import org.springframework.security.authentication.AuthenticationServiceException;
     7 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     8 import org.springframework.security.core.Authentication;
     9 import org.springframework.security.core.AuthenticationException;
    10 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    11 
    12 public class CustomLoginFilter extends UsernamePasswordAuthenticationFilter {
    13 
    14     public Authentication attemptAuthentication(HttpServletRequest request,
    15             HttpServletResponse response) throws AuthenticationException {
    16 
    17         // if (!request.getMethod().equals("POST")) {
    18         // throw new AuthenticationServiceException(
    19         // "Authentication method not supported: "
    20         // + request.getMethod());
    21         // }
    22 
    23         String username = obtainUsername(request).toUpperCase().trim();
    24         String password = obtainPassword(request);
    25 
    26         UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    27                 username, password);
    28 
    29         setDetails(request, authRequest);
    30         return this.getAuthenticationManager().authenticate(authRequest);
    31     }
    32 
    33 }
    View Code

    即:从UsernamePasswordAuthenticationFilter继承一个类,然后把关于POST方式判断的代码注释掉即可。默认情况下,Spring Security的用户名是区分大小写,如果觉得没必要,上面的代码同时还演示了如何在Filter中自动将其转换成大写。

    默认情况下,登录成功后,Spring Security有自己的handler处理类,如果想在登录成功后,加一点自己的处理逻辑,可参考下面的代码:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.io.IOException;
     4 
     5 import javax.servlet.ServletException;
     6 import javax.servlet.http.HttpServletRequest;
     7 import javax.servlet.http.HttpServletResponse;
     8 
     9 import org.springframework.security.core.Authentication;
    10 import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    11 
    12 public class CustomLoginHandler extends
    13         SavedRequestAwareAuthenticationSuccessHandler {
    14 
    15     @Override
    16     public void onAuthenticationSuccess(HttpServletRequest request,
    17             HttpServletResponse response, Authentication authentication)
    18             throws ServletException, IOException {
    19         super.onAuthenticationSuccess(request, response, authentication);
    20 
    21         //这里可以追加开发人员自己的额外处理
    22         System.out
    23                 .println("CustomLoginHandler.onAuthenticationSuccess() is called!");
    24     }
    25 
    26 }
    View Code

    类似的,要自定义LogoutFilter,可参考下面的代码:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import org.springframework.security.web.authentication.logout.LogoutFilter;
     4 import org.springframework.security.web.authentication.logout.LogoutHandler;
     5 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
     6 
     7 public class CustomLogoutFilter extends LogoutFilter {
     8 
     9     public CustomLogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) {
    10         super(logoutSuccessUrl, handlers);
    11     }
    12 
    13     public CustomLogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
    14             LogoutHandler[] handlers) {
    15         super(logoutSuccessHandler, handlers);
    16     }
    17 
    18 }
    View Code

    即:从LogoutFilter继承一个类,如果还想在退出后加点自己的逻辑(比如注销后,清空额外的Cookie之类记录退出时间、地点之类),可重写doFilter方法,但不建议这样,有更好的做法,自行定义logoutSuccessHandler,然后在运行时,通过构造函数注入即可。

    下面是自定义退出成功处理的handler示例:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import javax.servlet.http.HttpServletRequest;
     4 import javax.servlet.http.HttpServletResponse;
     5 
     6 import org.springframework.security.core.Authentication;
     7 import org.springframework.security.web.authentication.logout.LogoutHandler;
     8 
     9 public class CustomLogoutHandler implements LogoutHandler {
    10 
    11     public CustomLogoutHandler() {
    12     }
    13 
    14     @Override
    15     public void logout(HttpServletRequest request,
    16             HttpServletResponse response, Authentication authentication) {
    17         System.out.println("CustomLogoutSuccessHandler.logout() is called!");
    18 
    19     }
    20 
    21 }
    View Code

    这二个Filter弄好后,剩下的就是改配置:

     1 <beans:beans xmlns="http://www.springframework.org/schema/security"
     2     xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://www.springframework.org/schema/beans
     4     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     5     http://www.springframework.org/schema/security
     6     http://www.springframework.org/schema/security/spring-security-3.2.xsd">
     7 
     8     <http entry-point-ref="loginEntryPoint">
     9         <!-- 替换默认的LogoutFilter -->
    10         <custom-filter ref="customLogoutFilter" position="LOGOUT_FILTER" />
    11         <!-- 替换默认的LoginFilter -->
    12         <custom-filter ref="customLoginFilter" position="FORM_LOGIN_FILTER" />
    13         <intercept-url pattern="/admin" access="ROLE_USER" />
    14     </http>
    15 
    16     <authentication-manager alias="authenticationManager">
    17         ...
    18     </authentication-manager>
    19 
    20     <beans:bean id="loginEntryPoint"
    21         class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    22         <!-- 默认登录页的url -->
    23         <beans:constructor-arg value="/login" />
    24     </beans:bean>
    25 
    26     <beans:bean id="customLoginFilter" class="com.cnblogs.yjmyzz.CustomLoginFilter">
    27         <!-- 校验登录是否有效的虚拟url -->
    28         <beans:property name="filterProcessesUrl" value="/checklogin" />
    29         <beans:property name="authenticationManager" ref="authenticationManager" />
    30         <beans:property name="usernameParameter" value="username" />
    31         <beans:property name="passwordParameter" value="password" />
    32         <beans:property name="authenticationSuccessHandler">
    33             <!-- 自定义登录成功后的处理handler -->
    34             <beans:bean class="com.cnblogs.yjmyzz.CustomLoginHandler">
    35                 <!-- 登录成功后的默认url -->
    36                 <beans:property name="defaultTargetUrl" value="/welcome" />
    37             </beans:bean>
    38         </beans:property>
    39         <beans:property name="authenticationFailureHandler">
    40             <beans:bean
    41                 class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    42                 <!-- 登录失败后的默认Url -->
    43                 <beans:property name="defaultFailureUrl" value="/login?error" />
    44             </beans:bean>
    45         </beans:property>
    46     </beans:bean>
    47 
    48     <beans:bean id="customLogoutFilter" class="com.cnblogs.yjmyzz.CustomLogoutFilter">
    49         <!-- 处理退出的虚拟url -->
    50         <beans:property name="filterProcessesUrl" value="/logout" />
    51         <!-- 退出处理成功后的默认显示url -->
    52         <beans:constructor-arg index="0" value="/login?logout" />
    53         <beans:constructor-arg index="1">
    54             <!-- 退出成功后的handler列表 -->
    55             <beans:array>
    56                 <beans:bean id="securityContextLogoutHandler"
    57                     class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
    58                 <!-- 加入了开发人员自定义的退出成功处理 -->
    59                 <beans:bean id="customLogoutSuccessHandler" class="com.cnblogs.yjmyzz.CustomLogoutHandler" />
    60             </beans:array>
    61         </beans:constructor-arg>
    62     </beans:bean>
    63 
    64 </beans:beans>

    用户输入“用户名、密码”,并点击完登录后,最终实现校验的是AuthenticationProvider,而且一个webApp中可以同时使用多个Provider,下面是一个自定义Provider的示例代码:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.util.ArrayList;
     4 import java.util.Arrays;
     5 import java.util.Collection;
     6 
     7 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     8 import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
     9 import org.springframework.security.core.AuthenticationException;
    10 import org.springframework.security.core.GrantedAuthority;
    11 import org.springframework.security.core.authority.SimpleGrantedAuthority;
    12 import org.springframework.security.core.userdetails.User;
    13 import org.springframework.security.core.userdetails.UserDetails;
    14 
    15 public class CustomAuthenticationProvider extends
    16         AbstractUserDetailsAuthenticationProvider {
    17 
    18     @Override
    19     protected void additionalAuthenticationChecks(UserDetails userDetails,
    20             UsernamePasswordAuthenticationToken authentication)
    21             throws AuthenticationException {
    22         //如果想做点额外的检查,可以在这个方法里处理,校验不通时,直接抛异常即可
    23         System.out
    24                 .println("CustomAuthenticationProvider.additionalAuthenticationChecks() is called!");
    25     }
    26 
    27     @Override
    28     protected UserDetails retrieveUser(String username,
    29             UsernamePasswordAuthenticationToken authentication)
    30             throws AuthenticationException {
    31 
    32         System.out
    33                 .println("CustomAuthenticationProvider.retrieveUser() is called!");
    34 
    35         String[] whiteLists = new String[] { "ADMIN", "SUPERVISOR", "JIMMY" };
    36 
    37         // 如果用户在白名单里,直接放行(注:仅仅只是演示,千万不要在实际项目中这么干!)
    38         if (Arrays.asList(whiteLists).contains(username)) {
    39             Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    40             authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
    41             UserDetails user = new User(username, "whatever", authorities);
    42             return user;
    43         }
    44 
    45         return new User(username, "no-password", false, false, false, false,
    46                 new ArrayList<GrantedAuthority>());
    47 
    48     }
    49 
    50 }
    View Code

    这里仅仅只是出于演示目的,人为留了一个后门,只要用户名在白名单之列,不管输入什么密码,都可以通过!(再次提示:只是出于演示目的,千万不要在实际项目中使用

    相关的配置节点修改如下:

     1     <authentication-manager alias="authenticationManager">
     2         <authentication-provider>
     3             <user-service>
     4                 <user name="yjmyzz" password="123456" authorities="ROLE_USER" />
     5             </user-service>
     6         </authentication-provider>
     7         <!-- 加入开发人员自定义的Provider -->
     8         <authentication-provider ref="customProvider" />
     9     </authentication-manager>
    10 
    11     <beans:bean id="customProvider"
    12         class="com.cnblogs.yjmyzz.CustomAuthenticationProvider" />

    运行时,Spring Security将会按照顺序,依次从上向下调用所有Provider,只要任何一个Provider校验通过,整个认证将通过。这也意味着:用户yjmyzz/123456以及白名单中的用户名均可以登录系统。这是一件很有意思的事情,试想一下,如果有二个现成的系统,各有自己的用户名/密码(包括不同的存储机制),想把他们集成在一个登录页面使用,技术上讲,只要实现二个Provider各自对应不同的处理,可以很轻易的实现多个系统的认证集成。(注:当然实际应用中,多个系统的认证集成,更多的是采用SSO来处理,这里只是提供了另一种思路)

    最后来看下如何自定义AuthenticationToken,如果我们想在登录页上加一些额外的输入项(比如:验证码,安全问题之类),

    为了能让这些额外添加的输入项,传递到Provider中参与验证,就需要对UsernamePasswordAuthenticationToken进行扩展,参考代码如下:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     4 
     5 public class CustomAuthenticationToken extends
     6         UsernamePasswordAuthenticationToken {
     7 
     8     private static final long serialVersionUID = 5414106440823275021L;
     9 
    10     public CustomAuthenticationToken(String principal, String credentials,
    11             Integer questionId, String answer) {
    12         super(principal, credentials);
    13         this.answer = answer;
    14         this.questionId = questionId;
    15     }
    16 
    17     private String answer;
    18     private Integer questionId;
    19 
    20     public String getAnswer() {
    21         return answer;
    22     }
    23 
    24     public void setAnswer(String answer) {
    25         this.answer = answer;
    26     }
    27 
    28     public Integer getQuestionId() {
    29         return questionId;
    30     }
    31 
    32     public void setQuestionId(Integer questionId) {
    33         this.questionId = questionId;
    34     }
    35 
    36 }
    View Code

    这里扩展了二个属性:questionId、answer,为了方便后面“诗句问题"的回答进行判断,还得先做点其它准备工作

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.util.Hashtable;
     4 
     5 public class LoginQuestion {
     6 
     7     private static Hashtable<Integer, String> questionTable = new Hashtable<Integer, String>();
     8 
     9     public static Hashtable<Integer, String> getQuestions() {
    10         if (questionTable.size() <= 0) {
    11             questionTable.put(1, "葡萄美酒夜光杯/欲饮琵琶马上催");
    12             questionTable.put(2, "故人西辞黄鹤楼/烟花三月下扬州");
    13             questionTable.put(3, "孤帆远影碧空尽/唯见长江天际流");
    14             questionTable.put(4, "相见时难别亦难/东风无力百花残");
    15             questionTable.put(5, "渔翁夜傍西岩宿/晓汲清湘燃楚竹");
    16         }
    17         return questionTable;
    18     }
    19 
    20 }
    View Code

    预定义了几句唐诗,key即为questionId,value为 "题目/答案"格式。此外,如果答错了,为了方便向用户提示错误原因,还要定义一个异常类:(注:Spring Security中,所有验证失败,都是通过直接抛异常来处理的)

     1 package com.cnblogs.yjmyzz;
     2 
     3 import org.springframework.security.core.AuthenticationException;
     4 
     5 public class BadAnswerException extends AuthenticationException {
     6 
     7     private static final long serialVersionUID = -3333012976129153127L;
     8 
     9     public BadAnswerException(String msg) {
    10         super(msg);
    11 
    12     }
    13 
    14 }
    View Code

    原来的CustomLoginFilter也要相应的修改,以接收额外添加的二个参数:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.io.UnsupportedEncodingException;
     4 
     5 import javax.servlet.http.HttpServletRequest;
     6 import javax.servlet.http.HttpServletResponse;
     7 import org.springframework.security.core.Authentication;
     8 import org.springframework.security.core.AuthenticationException;
     9 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    10 
    11 public class CustomLoginFilter extends UsernamePasswordAuthenticationFilter {
    12 
    13     public Authentication attemptAuthentication(HttpServletRequest request,
    14             HttpServletResponse response) throws AuthenticationException {
    15 
    16         //解决中文诗句的post乱码问题
    17         try {
    18             request.setCharacterEncoding("UTF-8");
    19         } catch (UnsupportedEncodingException e) {
    20             e.printStackTrace();
    21         }
    22 
    23         // if (!request.getMethod().equals("POST")) {
    24         // throw new AuthenticationServiceException(
    25         // "Authentication method not supported: "
    26         // + request.getMethod());
    27         // }
    28 
    29         String username = obtainUsername(request).toUpperCase().trim();
    30         String password = obtainPassword(request);
    31         //获取用户输入的下一句答案
    32         String answer = obtainAnswer(request);
    33         //获取问题Id(即: hashTable的key)
    34         Integer questionId = obtainQuestionId(request);
    35 
    36         //这里将原来的UsernamePasswordAuthenticationToken换成我们自定义的CustomAuthenticationToken
    37         CustomAuthenticationToken authRequest = new CustomAuthenticationToken(
    38                 username, password, questionId, answer);
    39 
    40         //这里就将token传到后续验证环节了
    41         setDetails(request, authRequest);
    42         return this.getAuthenticationManager().authenticate(authRequest);
    43     }
    44 
    45     protected String obtainAnswer(HttpServletRequest request) {
    46         return request.getParameter(answerParameter);
    47     }
    48 
    49     protected Integer obtainQuestionId(HttpServletRequest request) {
    50         return Integer.parseInt(request.getParameter(questionIdParameter));
    51     }
    52 
    53     private String questionIdParameter = "questionId";
    54     private String answerParameter = "answer";
    55 
    56     public String getQuestionIdParameter() {
    57         return questionIdParameter;
    58     }
    59 
    60     public void setQuestionIdParameter(String questionIdParameter) {
    61         this.questionIdParameter = questionIdParameter;
    62     }
    63 
    64     public String getAnswerParameter() {
    65         return answerParameter;
    66     }
    67 
    68     public void setAnswerParameter(String answerParameter) {
    69         this.answerParameter = answerParameter;
    70     }
    71 
    72 }
    View Code

    现在,CustomAuthenticationProvider中的additionalAuthenticationChecks方法中,就能拿到用户提交的下一句答案,进行相关验证了:

     1     @Override
     2     protected void additionalAuthenticationChecks(UserDetails userDetails,
     3             UsernamePasswordAuthenticationToken authentication)
     4             throws AuthenticationException {
     5         // 转换为自定义的token
     6         CustomAuthenticationToken token = (CustomAuthenticationToken) authentication;
     7         String poem = LoginQuestion.getQuestions().get(token.getQuestionId());
     8         // 校验下一句的答案是否正确
     9         if (!poem.split("/")[1].equals(token.getAnswer())) {
    10             throw new BadAnswerException("the answer is wrong!");
    11         }
    12 
    13     }
    View Code

    最后来处理前端的login页面及Action

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.util.Random;
     4 
     5 import javax.servlet.http.HttpServletRequest;
     6 
     7 import org.springframework.security.authentication.BadCredentialsException;
     8 import org.springframework.security.authentication.LockedException;
     9 import org.springframework.stereotype.Controller;
    10 import org.springframework.web.bind.annotation.RequestMapping;
    11 import org.springframework.web.bind.annotation.RequestMethod;
    12 import org.springframework.web.bind.annotation.RequestParam;
    13 import org.springframework.web.servlet.ModelAndView;
    14 
    15 @Controller
    16 public class HelloController {
    17 
    18     @RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
    19     public ModelAndView welcome() {
    20 
    21         ModelAndView model = new ModelAndView();
    22         model.addObject("title",
    23                 "Welcome - Spring Security Custom login/logout Filter");
    24         model.addObject("message", "This is welcome page!");
    25         model.setViewName("hello");
    26         return model;
    27 
    28     }
    29 
    30     @RequestMapping(value = "/admin", method = RequestMethod.GET)
    31     public ModelAndView admin() {
    32 
    33         ModelAndView model = new ModelAndView();
    34         model.addObject("title",
    35                 "Admin - Spring Security Custom login/logout Filter");
    36         model.addObject("message", "This is protected page!");
    37         model.setViewName("admin");
    38 
    39         return model;
    40 
    41     }
    42 
    43     @RequestMapping(value = "/login", method = RequestMethod.GET)
    44     public ModelAndView login(
    45             @RequestParam(value = "error", required = false) String error,
    46             @RequestParam(value = "logout", required = false) String logout,
    47             HttpServletRequest request) {
    48 
    49         ModelAndView model = new ModelAndView();
    50         if (error != null) {
    51             model.addObject("error",
    52                     getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION"));
    53         }
    54 
    55         if (logout != null) {
    56             model.addObject("msg", "You've been logged out successfully.");
    57         }
    58 
    59         //从预定义的诗句中,随机挑一个上句
    60         Random rnd = new Random();
    61         int questionId = rnd.nextInt(LoginQuestion.getQuestions().size() + 1);
    62         if (questionId == 0) {
    63             questionId = 1;
    64         }
    65         model.addObject("questionId", questionId);
    66         model.addObject("question", LoginQuestion.getQuestions()
    67                 .get(questionId).split("/")[0]);
    68         
    69         model.setViewName("login");
    70 
    71         return model;
    72 
    73     }
    74 
    75     private String getErrorMessage(HttpServletRequest request, String key) {
    76         Exception exception = (Exception) request.getSession()
    77                 .getAttribute(key);
    78         String error = "";
    79         if (exception instanceof BadCredentialsException) {
    80             error = "Invalid username and password!";
    81         } else if (exception instanceof BadAnswerException) {
    82             error = exception.getMessage();
    83         } else if (exception instanceof LockedException) {
    84             error = exception.getMessage();
    85         } else {
    86             error = "Invalid username and password!";
    87         }
    88 
    89         return error;
    90     }
    91 
    92 }
    View Code

    代码很简单,从预定义的诗句中,随机挑一句,并把questionId及question放到model中,传给view

     1 <%@ page language="java" contentType="text/html; charset=UTF-8"
     2     pageEncoding="UTF-8"%>
     3 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
     4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
     5 <html>
     6 <head>
     7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     8 <title>Login Page</title>
     9 <link rel="Stylesheet" type="text/css"
    10     href="${pageContext.request.contextPath}/resources/css/login.css" />
    11 </head>
    12 <body onload='document.loginForm.username.focus();'>
    13     <h1>Spring Security CustomFilter(XML)</h1>
    14 
    15     <div id="login-box">
    16 
    17         <c:if test="${not empty error}">
    18             <div class="error">${error}</div>
    19         </c:if>
    20         <c:if test="${not empty msg}">
    21             <div class="msg">${msg}</div>
    22         </c:if>
    23         <form name='loginForm' action="<c:url value='checklogin' />"
    24             method='POST'>
    25             <table>
    26                 <tr>
    27                     <td>User:</td>
    28                     <td><input type='text' name='username' value=''></td>
    29                 </tr>
    30                 <tr>
    31                     <td>Password:</td>
    32                     <td><input type='password' name='password' /></td>
    33                 </tr>
    34                 <tr>
    35                     <td valign="top">Question:</td>
    36                     <td>诗句<span style="color:red">"${question}"</span><br/>的下一句是什么?<br /> <input type='text'
    37                         name='answer' value=''>
    38                     </td>
    39                 </tr>
    40                 <tr>
    41                     <td colspan='2'><input name="submit" type="submit"
    42                         value="submit" /></td>
    43                 </tr>
    44             </table>
    45             <input type="hidden" name="${_csrf.parameterName}"
    46                 value="${_csrf.token}" /> <input type="hidden" name="questionId"
    47                 value="${questionId}" />
    48         </form>
    49     </div>
    50 </body>
    51 </html>
    View Code

    ok,完工!

    不过,有一个小问题要提醒一下:对本文所示案例而言,因为同时应用了二个Provider,一个是默认的,一个是我们后来自定义的,而对"下一句"的答案验证,只在CustomAuthenticationProvider中做了处理,换句话说,如果用户在界面上输入的用户名/密码是yjmyzz/123456,根据前面讲到的规则,默认的Provider会先起作用,认证通过直接忽略”下一句“的验证,只有输入白名单中的用户名时,才会走CustomAuthenticationProvider的验证流程。

    国际惯例,最后附上示例源代码:SpringSecurity-CustomFilter.zip

  • 相关阅读:
    Linux如何对文件内容中的关键字进行查找
    Gitlab如何进行备份恢复与迁移?
    Centos7上传文件和下载文件命令
    Linux下如何查看系统启动时间和运行时间
    您应该知道的35个绝对重要的Linux命令
    rabbitMq可靠消息投递之交换机备份
    rabbitMq可靠性投递之配置(消息至交换机,至队列不通的回调)
    springcloud超时重试机制的先后顺序
    mysql 8.0 1405的坑
    linux安装mysql8.0
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/how-to-custom-filter-provider-and-token-in-spring-security3.html
Copyright © 2011-2022 走看看