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

首先给大家推荐一下我老师大神的人工智能教学网站。教学不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵黄段子!点这里可以跳转到网站

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

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

调整目录结构

原目录

原目录结构:

删除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>

测试

登录界面

分别用手机号+验证码、用户名+密码登录。这样看起来更有条理些。

总结

目录结构

当前目录结构

修改后的多认证方式,更方便扩展。假如要增加一个邮箱+验证码。分成以下几步:
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的认证,不至于出现意想不到的问题。

点这里可以跳转到人工智能网站

发表评论