【Spring Security + OAuth2 + JWT入门到实战】4. 系统配置自定义登录页面

关于个性化配置全部在spring-security-browser项目的BrowserSecurityConfig完成。

自定义登录页面

创建登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>标准登录页面</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

注意这里的路径:acrion="/authentication/form";路径是自定义的, 
而UsernamePasswordAuthenticationFilter默认是处理/login路径的登录请求。下面配置解决这个问题

public UsernamePasswordAuthenticationFilter() {
  super(new AntPathRequestMatcher("/login", "POST"));
}

 

在BrowserSecurityConfig类重写configure方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    //配置
}
package com.spring.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置
        http
                .formLogin()
                .loginPage("/signIn.html")//登录页面路径
                // 处理登录请求路径
                .loginProcessingUrl("/authentication/form")
                .and()
                .authorizeRequests() // 授权配置
                //不需要认证的路径
                .antMatchers("/signIn.html").permitAll()
                .anyRequest() // 所有请求
                .authenticated() // 都需要认证
                .and().csrf().disable();
    }
}

 

启动项目测试:http://127.0.0.1:8080/hello

 处理不同类型的请求

我们做的是一个可重复使用的框架登录页面可能是多个,现在解决这个问题

在spring-security-browser项目创建自定义Controller

package com.spring.security;

import com.spring.security.support.SimpleResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
public class BrowserSecurityController {
    // 封装了引发跳转请求的工具类,从session中获取
    private RequestCache requestCache = new HttpSessionRequestCache();

    // spring的工具类:封装了所有跳转行为策略类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();


    /**
     * 需要身份认证时跳转到这里
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requirAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        // 如果有引发认证的请求
        // spring 在跳转前应该会把信息存放在某个地方?
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            System.out.println("引发跳转的请求:" + targetUrl);
            // 如果是html请求,则跳转到登录页
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, "这里填写要跳转的页面先不写,等等从配置文件读取");
            }
        }
        // 否则都返回需要认证的json串
        return new SimpleResponse("访问的服务需要身份认证!");
    }
}

在spring-security-browser项目创建返回值类

package com.spring.security.support;

import lombok.Data;

@Data
public class SimpleResponse {
    private Object content;

    public SimpleResponse(Object content) {
        this.content = content;
    }
}

解决自定义跳转的登录页面,读取yml

修改spring-security-demo项目application.yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1/auto_test?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
hk:
  security:
    browser:
      loginPage: /demoLogin.html  #登录页面

关于系统配置的封装,SecurityProperties最外层封装包含BrowserProperties(浏览器相关系统配置),ValidateCodeProperties(验证码相关系统配置),OAuth2Properties(权限相关系统配置);

在spring-security-core项目新建SecurityProperties类:

package com.spring.security.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 安全属性
 */
@Data
@ConfigurationProperties(prefix = "hk.security")
public class SecurityProperties {

    private BrowserProperties browser = new BrowserProperties();

}

同目录BrowserProperties类

package com.spring.security.properties;

import lombok.Data;

/**
 * 浏览器的属性
 */
@Data
public class BrowserProperties {

    /**
     * 登录页面   默认登录页signIn.html
     */
    private String loginPage = "/signIn.html";
}

要想让上面的配置生效还需要加一个SecurityCoreConfig类

在core项目新建:

package com.spring.security.config;

import com.spring.security.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * 安全核心配置
 * SecurityProperties  生效
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

改造BrowserSecurityController读取系统配置

package com.spring.security;

import com.spring.security.properties.SecurityProperties;
import com.spring.security.support.SimpleResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
public class BrowserSecurityController {
    // 封装了引发跳转请求的工具类,从session中获取
    private RequestCache requestCache = new HttpSessionRequestCache();

    // spring的工具类:封装了所有跳转行为策略类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 需要身份认证时跳转到这里
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requirAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        // 如果有引发认证的请求
        // spring 在跳转前应该会把信息存放在某个地方?
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            System.out.println("引发跳转的请求:" + targetUrl);
            // 如果是html请求,则跳转到登录页
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
            }
        }
        // 否则都返回需要认证的json串
        return new SimpleResponse("访问的服务需要身份认证!");
    }
}

现在demo项目系统配置登录页面为:demoLogin.html

demo项目新建demoLogin.html页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo登录页面</title>
</head>
<body>
<h2>demo登录页面</h2>
</body>
</html>

改造BrowserSecurityConfig类不拦截登录页

package com.spring.security;

import com.spring.security.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置
        http
                .formLogin()
                .loginPage("/authentication/require")//登录页面路径
                // 处理登录请求路径
                .loginProcessingUrl("/authentication/form")
                .and()
                .authorizeRequests() // 授权配置
                //不需要认证的路径
                .antMatchers("/authentication/require","/signIn.html",securityProperties.getBrowser().getLoginPage()).permitAll()
                .anyRequest() // 所有请求
                .authenticated() // 都需要认证
                .and().csrf().disable();
    }
}

启动项目访问:http://127.0.0.1:8080/hello

访问:http://127.0.0.1:8080/index.html

注释掉系统配置:

#hk:
#  security:
#    browser:
#      loginPage: /demoLogin.html  #登录页面

重启项目访问:http://127.0.0.1:8080/index.html