`

Spring Security笔记:自定义Login/Logout Filter、AuthenticationProvider、AuthenticationTo

 
阅读更多

http://www.cnblogs.com/yjmyzz/p/how-to-custom-filter-provider-and-token-in-spring-security3.html

 

 

下面是Spring Security Filter Chain的列表:

 

Table 1. Standard Filter Aliases and Ordering Alias Filter Class Namespace 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的方式直接登录,可以参考下面的代码:

 

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 }
复制代码

 

类似的,要自定义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 }
复制代码

 

即:从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 }
复制代码

 

这二个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 }
复制代码

 

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

 

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

 

复制代码
 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 }
复制代码

 

这里扩展了二个属性: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 }
复制代码

 

预定义了几句唐诗,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 }
复制代码

 

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

 

View Code

 

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

 

View Code

 

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

 

View Code

 

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

 

View Code

 

ok,完工!

 

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

 

 
 
分享到:
评论

相关推荐

    springsecurity_logout.rar

    在SpringSecurity中,登出(logout)功能是应用中不可或缺的一部分,它允许用户安全地结束当前会话,清除相关认证信息。下面将详细探讨如何使用SpringSecurity的原生logout功能,并结合给定的链接资源进行讲解。 ...

    Spring security 自定义密码加密方式的使用范例。

    1. **Filter Security Chain**:这是Spring Security的核心,一系列过滤器负责拦截请求,执行安全相关的操作。 2. **Authentication Manager**:负责处理用户的认证请求,包括验证用户名和密码。 3. **...

    Spring Security 新手入门级maven实例

    &lt;security:logout/&gt; &lt;/security:http&gt; &lt;security:authentication-manager&gt; &lt;security:authentication-provider&gt; &lt;security:user-service&gt; &lt;security:user name="user" password="password" authorities="ROLE...

    初识 Spring Security - v1.1.pdf

    - **Spring Security 定义好的核心Filter**:包括一系列内置过滤器,如`UsernamePasswordAuthenticationFilter`等。 ##### 10. **退出登录** - **概念**:提供了退出登录的功能,清除用户的认证状态。 ##### 11. ...

    SpringSecurity退出功能实现的正确方式(推荐)

    虽然 Spring Security 默认使用了 `/logout` 作为退出处理请求路径,登录页面作为退出之后的跳转页面。但是,有的时候我们需要一些个性化设置,例如: * 通过指定 `logoutUrl` 配置改变退出请求的默认路径。 * 通过...

    spring3中增加 spring security控制权限

    在Spring框架中,Spring Security是一个强大的安全访问控制组件,它为Web应用提供了全面的安全管理解决方案。本篇文章将深入探讨如何在Spring 3中整合Spring Security来实现权限控制,以保护你的应用程序免受非法...

    JAAS简介及示例代码

    在Java中,你可以创建自定义的登录模块,继承`javax.security.auth.spi.LoginModule`接口并实现其方法。例如,你可以创建一个名为`MyLoginModule`的类,如下所示: ```java public class MyLoginModule implements ...

    SpringSecurity笔记2-SpringSecurity命名空间

    在"SpringSecurity笔记2-SpringSecurity命名空间"的学习中,还会涉及到如何自定义过滤器链,以及如何通过`&lt;custom-filter&gt;`元素插入自定义的SpringSecurity过滤器。同时,理解`&lt;access-denied-handler&gt;`和`...

    SpringMvc 基于 kisso 的 sso 演示 demo

    3、退出访问 http://localhost:8080/logout 查看 kisso cookie 消失 权限 1、登录访问 http://localhost:8080/test/permission/index.html 有权限显示登录用户 2、测试无权限访问 ...

    springboot+shiro+mybatis-plus纯净版框架(附带所需数据库sql)

    JDK1.8 springboot框架(2.7.17) shiro实现了简易版的控制登录和角色权限(1.13.0) mybatis-plus实现了简易版的controller,service,mapper自动生成和分页(3.4.0) ...3、登出:http://127.0.0.1:8081/logout

    spring security的应用和配置

    ity.xml 文件,这是 Spring Security 的核心配置文件,用于定义安全策略和过滤器链。下面是一个基本的配置示例,展示了如何设置用户认证和授权: ```xml &lt;beans:beans xmlns=...

    基于Java开发的OA办公审批系统源码+项目详细说明.zip

    - spring-security:spring-security业务模块 - model:实体类模块 - service-oa:系统服务模块 四、接口汇总 1. 角色管理--获取所有角色:/admin/system/sysRole/findAll 2. 角色管理--分页查询:/admin/...

    springboot+springsecurity入门

    SpringBoot和SpringSecurity是Java开发领域中两个非常重要的框架,它们在构建现代Web应用程序时起着关键作用。SpringBoot简化了Spring应用的初始搭建以及开发过程,而SpringSecurity则为应用提供了全面的安全管理...

    SpringMvc 基于 kisso 实现的 sso 演示 demo代码完整版

    3、退出访问 http://localhost:8080/logout 查看 kisso cookie 消失 ``` # 权限 ``` 1、登录访问 http://localhost:8080/test/permission/index.html 有权限显示登录用户 2、测试无权限访问 ...

    spring security 3 多页面登录 小秘密小运气

    Spring Security允许通过扩展`AuthenticationProvider`来实现自定义的身份验证逻辑。例如,你可能需要根据不同的登录页面执行不同的验证规则,或者从不同的数据源获取用户信息。 3. **定制错误处理**: 如果登录...

    Spring-Boot结合Security+JWT 简单实现前后端分离用户登录、授权案例

    Spring-Security结合JWT 实现前后端分离完成权限验证功能案例,案例中,主要完成用户登录获取Token,通过Token访问Rest接口,没有权限或授权失败时返回JSON,前端根据状态码进行重新登录;案例中的用户名称: jake_j...

    spring_security_demo

    &lt;logout logout-url="/j_spring_security_logout" /&gt; &lt;/http&gt; &lt;!-- ...其他配置... --&gt; &lt;/authentication-provider&gt; &lt;/authentication-manager&gt; &lt;/beans:beans&gt; ``` 在这个例子中,`/admin/**` 的 URL ...

    spring security3配置

    &lt;logout/&gt; &lt;remember-me/&gt; &lt;concurrency-control max-sessions="10" error-if-maximum-exceeded="true"/&gt; &lt;/session-management&gt; &lt;custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/&gt; &lt;!...

    spring-security多个登录页面配置

    在Spring Security框架中实现多个登录页面的配置是一项高级特性,主要应用于区分前端用户与后端管理员的不同登录需求。本文将详细介绍如何通过Spring Security配置多个登录页面,并为不同类型的用户设置不同的登录...

Global site tag (gtag.js) - Google Analytics