欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring security(SpringBoot)自定义Provider实现多种认证方式

程序员文章站 2024-03-26 09:06:17
...

转载请注明出处spring boot 实例之 多登录方式–碧波之心简书

上一篇:spring boot 实例之 用户登录。经过对spring security再次分析,觉得上一篇的实现方式有些不合理。不应该把UsernamePasswordAuthenticationFilter给替换了。今天我们来优化一下。让登录的扩展更合理一些。

调整目录结构

原目录结构:
spring security(SpringBoot)自定义Provider实现多种认证方式
删除SecurityController.java文件,换成用mvc config的方式来跳转到登录页面。

package com.biboheart.demo.user.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SecurityMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
}

删除BhAuthenticationFilter.java。

创建过滤器

在filter中增加MobileCodeAuthenticationProcessingFilter。这里过滤的是”/mobileCodeLogin”的请求,用不同的请求地址来区分不同的登录认证方式。

package com.biboheart.demo.user.security.filter;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.biboheart.demo.user.security.tokens.MobileCodeAuthenticationToken;

public class MobileCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";

    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
    private boolean postOnly = true;

    public MobileCodeAuthenticationProcessingFilter() {
        super(new AntPathRequestMatcher("/mobileCodeLogin", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String mobile = obtainMobile(request);
        String code = obtainCode(request);

        if (mobile == null) {
            mobile = "";
        }

        if (code == null) {
            code = "";
        }

        mobile = mobile.trim();
        code = code.trim();

        AbstractAuthenticationToken authRequest = new MobileCodeAuthenticationToken(mobile, code);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }

    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(codeParameter);
    }

    protected void setDetails(HttpServletRequest request,
            AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

}

修改SecurityConfiguration

主要修改过滤器的插入位置

package com.biboheart.demo.user.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.biboheart.demo.user.security.filter.MobileCodeAuthenticationProcessingFilter;
import com.biboheart.demo.user.security.provider.MobileCodeAuthenticationProvider;
import com.biboheart.demo.user.security.provider.UsernamePasswordAuthenticationProvider;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .authenticationProvider(mobileCodeAuthenticationProvider())
            .authenticationProvider(usernamePasswordAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
        http.addFilterBefore(mobileCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
        // @formatter:on
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public MobileCodeAuthenticationProcessingFilter mobileCodeAuthenticationProcessingFilter() {
        MobileCodeAuthenticationProcessingFilter filter = new MobileCodeAuthenticationProcessingFilter();
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Bean
    public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
        return new UsernamePasswordAuthenticationProvider();
    }

    @Bean
    public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {
        return new MobileCodeAuthenticationProvider();
    }
}

修改页面

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example</title>
</head>
<body>
    <h1>Welcome!</h1>
    <p>
        点击 <a th:href="@{/hello}">这里</a> 查看信息.
    </p>
</body>
</html>

hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Hello World!</title>
</head>
<body>
    <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
    <a href="/logout">注销</a>
    <form th:action="@{/logout}" method="post">
        <input type="submit" value="登出" />
    </form>
</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example</title>
</head>
<body>
    <div th:if="${param.error}">用户名或密码错误</div>
    <div th:if="${param.logout}">成功退出登录</div>
    <form th:action="@{/login}" method="post">
        <div>
            <label> 用户名: <input type="text" name="username" />
            </label>
        </div>
        <div>
            <label> 密码: <input type="password" name="password" />
            </label>
        </div>
        <div>
            <input type="submit" value="登录" />
        </div>
    </form>
    <span>-------------用手机号验证码登录-------------</span>
    <form th:action="@{/mobileCodeLogin}" method="post">
        <div>
            <label> 手机号: <input type="text" name="mobile" />
            </label>
        </div>
        <div>
            <label> 验证码: <input type="text" name="code" />
            </label>
        </div>
        <div>
            <input type="submit" value="登录" />
        </div>
    </form>
</body>
</html>

测试

分别用手机号+验证码、用户名+密码登录。这样看起来更有条理些。
spring security(SpringBoot)自定义Provider实现多种认证方式

总结

当前目录结构
spring security(SpringBoot)自定义Provider实现多种认证方式
修改后的多认证方式,更方便扩展。假如要增加一个邮箱+验证码。分成以下几步:
1. 增加一个token (EmailCodeAuthenticationToken)
2. 增加一个provider (EmailCodeAuthenticationProvider)
3. 增加一个filter (EmailCodeAuthenticationProcessingFilter)
4. 在SecurityConfiguration中把三个项加入相应的位置

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .authenticationProvider(mobileCodeAuthenticationProvider())
            .authenticationProvider(emailCodeAuthenticationProvider())
            .authenticationProvider(usernamePasswordAuthenticationProvider());
    }
    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        ...
        http.addFilterBefore(mobileCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(emailCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
        // @formatter:on
    }
    ...

    @Bean
    public EmailCodeAuthenticationProcessingFilter emailCodeAuthenticationProcessingFilter() {
        EmailCodeAuthenticationProcessingFilter filter = new EmailCodeAuthenticationProcessingFilter();
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }
    ...

    @Bean
    public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {
        return new EmailCodeAuthenticationProvider();
    }

用户名+密码的认证就保留了原有的业务。每一个认证的增加是各自独立的。后续要增加的Oauth2认证还要用到usernamePasswordAuthenticationProvider的认证,不至于出现意想不到的问题。