Spring Security 使用自定义控制器来完成登陆验证

Spring Security 下面简称为 Security 基于 spring-security 4.1

Security 的 WEB 扩展中 form 方式登陆使用的是过滤器方式,页面模版是可以定制的,但是如果需要登陆表单中有更多的选项,或者说需要在登陆的时候处理一些事情就变的很不方便。下面就是教你如何使用一个普通的控制器来完成登陆验证。

首先是制定 Security 的配置文件

http 节点中有一个属性 entry-point-ref 可以指定如果需要登陆将如何反应,在实现 AuthenticationEntryPoint 接口的类中有一个名称叫 LoginUrlAuthenticationEntryPoint 的类他可以实现需要登陆的时候产生 URL 跳转。

首先创建一个 LoginUrlAuthenticationEntryPoint bean

<beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <!-- 构造函数中指定需要跳转的 URL -->
    <beans:constructor-arg value="/login" />
</beans:bean>

然后在 http 节点中配置 entry-point-ref
auto-config 可以关闭,因为不需要自动配置

<http auto-config="false" entry-point-ref="authenticationEntryPoint">
    ...
</http>

http 节点内添加 intercept-url 防止拦截 /login 链接

<intercept-url pattern="/login" access="permitAll" />

如果需要记住我功能,需要在 http 节点内增加 remember-me 配置

<!-- rememberMe 对应的是表单类中的属性名称 -->
<remember-me remember-me-parameter="rememberMe" />

最后可以增加一个退出过滤器
因为是过滤器拦截判断使用 /logout 不需要有对应的控制器
如果这个 /logout 中没有对应的控制器,需要添加 logout-success-url 跳转链接,防止访问 /logout URL 后候报 404 错误

<logout logout-url="/logout" logout-success-url="/" />

最后需要配置 authentication-manager 节点中的 id 用来给控制器中注入使用

<authentication-manager id="authenticationManager">
   ...
</authentication-manager>

完整的配置文件代码

<?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.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd">
    
    <!-- 本配置文件完全使用自定的控制器来完成登陆退出操作 Yefei -->
    
    <!-- 开启 Spring Security 调试模式
    <debug />
    -->

    <!-- 是否开启注解支持,例如: @Secured
    <global-method-security secured-annotations="enabled" />
    -->

    <!-- 配置不需要安全过滤的页面 -->
    <http pattern="/static/**" security="none" />

    <beans:bean id="authenticationEntryPoint"
        class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:constructor-arg value="/login" />
    </beans:bean>

    <http auto-config="false"
        entry-point-ref="authenticationEntryPoint">
        <intercept-url pattern="/" access="permitAll"/>
        <intercept-url pattern="/login" access="permitAll" />
        <intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
        <intercept-url pattern="/**" access="hasRole('USER')" />
        <!-- 用于 cookie 登陆 remember-me-parameter 中的值必须和表单中的 rememberMe name 一致 -->
        <remember-me remember-me-parameter="rememberMe" />
        <!-- logout 可以使用简单的过滤器完成, 启用了 CSRF 必须使用 POST 退出 -->
        <logout logout-url="/logout" logout-success-url="/" />
    </http>
    
    <authentication-manager id="authenticationManager">
        <authentication-provider>
            <user-service>
                <user name="admin" authorities="ROLE_USER,ROLE_ADMIN" password="123456" />
                <user name="user" authorities="ROLE_USER" password="123456" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
    
</beans:beans>

下面是控制器的代码部分

代码过程都是通过 debug 模式下在 UsernamePasswordAuthenticationFilter 中分析得出

@Controller
public class AuthController {
    @Autowired
    @Qualifier("authenticationManager") // bean id 在 <authentication-manager> 中设置
    private AuthenticationManager authManager;
 
    @Autowired
    private SessionAuthenticationStrategy sessionStrategy;
 
    @Autowired(required = false)
    private RememberMeServices rememberMeServices;
 
    @Autowired(required = false)
    private ApplicationEventPublisher eventPublisher;
 
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(LoginForm form) {
        return "login";
    }
 
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String loginPost(@Valid LoginForm form, BindingResult result,
            HttpServletRequest request, HttpServletResponse response) {
        if (!result.hasErrors()) {
 
            // 创建一个用户名密码登陆信息
            UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(form.getUsername(), form.getPassword());
 
            try {
                // 用户名密码登陆效验
                Authentication authResult = authManager.authenticate(token);
 
                // 在 session 中保存 authResult
                sessionStrategy.onAuthentication(authResult, request, response);
 
                // 在当前访问线程中设置 authResult
                SecurityContextHolder.getContext().setAuthentication(authResult);
 
                // 如果记住我在配置文件中有配置
                if (rememberMeServices != null) {
                    rememberMeServices.loginSuccess(request, response, authResult);
                }
 
                // 发布登陆成功事件
                if (eventPublisher != null) {
                    eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
                }
                return "redirect:/";
            } catch (AuthenticationException e) {
                result.reject("authentication.exception", e.getLocalizedMessage());
            }
        }
        return "login";
    }
}

最后是 JSP 页面部分代码

页面都需要经过 Security 过滤器才能产生 csrf token,否则请关闭 csrf

登陆代码

<form:form commandName="loginForm" method="POST">
    <form:errors path="" />
    <p>用户名:<form:input path="username"/> <form:errors path="username" /></p>
    <p>密码:<form:input path="password"/> <form:errors path="password" /></p>
    <p>记住我:<form:checkbox path="rememberMe"/> <form:errors path="rememberMe" /></p>
    <button type="submit">登陆</button>
</form:form>

退出代码

<form action="/logout" method="POST">
    <sec:csrfInput/>
    <button>退出</button>
</form>

原创文章转载注明

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 117,993评论 14 132
  • 19年6月更新:该篇翻译已经被移到 TangYefei's Blog,相较于简书阅读体验会更好,如果喜欢请点个 ❤...
    tangyefei阅读 33,190评论 22 257
  • 引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的完结篇,前面三篇已经将认证鉴权与API权...
    aoho阅读 2,366评论 0 15
  • 我好像见过爱情的样子 一个容易遗忘的傻瓜 我也见过悲伤的样子 一树秋凉的青芽 我想靠近时间多一点 天空就悄悄落下 ...
    摩崖墨羽阅读 142评论 0 3
  • 东方的吐白做黎明的祷告者 阳光的照耀祝福每一个相信幸福的人 金灿灿撒下在河面上,泛着鱼鳞的形状 清脆的鸟声徘徊在树...
    鹿妧远阅读 77评论 0 5