1.界面
- 
简介
通过之前的章节实现了自定义登录认证,但是登录的界面是框架提供的,有时候更希望是通过自定义登录界面,接下来就来实现自定义登录界面
 - 
配置
复制
spring-security-config-account项目,修改名字为spring-security-login-page- 
修改启动类内容如下
package com.briup.security; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @SpringBootApplication class SpringSecurityLoginPageApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityLoginPageApplication.class, args); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } - 
修改
pom.xml文件,修改的内容如下image-20210120102150914 - 
测试
访问地址:http://127.0.0.1:9999/hello/test
跳转到默认提供的登录界面
image-20210120102332475 
 - 
自定义登录界面
要想实现自定义登录界面需要以下两步:
- 撰写登录界面
 - 修改
security配置类 
接下来就来挨个实现这两个步骤
撰写登录界面
在
src/mainresources新建static目录,并且在该目录下新建login.html,内容如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录界面</title> </head> <body> <h1>登录</h1> <!-- 请求方式必须为 post --> <form action="user/login" method="post"> <!-- name 属性值必须为 username --> 用户名: <input type="text" name="username"><br> <!-- password 属性值必须为 password --> 密码: <input type="password" name="password"><br> <input type="submit" value="提交"> </form> </body> </html>了解(start)
表单的用户名和密码的
name属性值必须为username和password,具体原因如下:UsernamePasswordAuthenticationFilter中,获取用户名和密码- 
该过滤器获取用户名密码则根据
username和password获取,如下image-20210120103920614同时还限制了其请求方式为
post 
了解(end)
修改配置类
找到配置类,进行修改,修改的内容如下:
package com.briup.security.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表示使用表单进行登录 .loginPage("/login.html") // 表示登录界面地址 .loginProcessingUrl("/user/login") //表单登录地址 .and() // 拼接 条件 .authorizeRequests() // 设置需要认证的请求地址 .antMatchers("/","/login.html","/user/login").permitAll() // 设置 不需要认证的请求 .anyRequest().authenticated() // 任何请求都需要认证 .and() .csrf().disable(); // 关闭 security 的 csrf防护 } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 设置认证逻辑为用户自定义认证逻辑 * 设置密码加密处理器为 BCryptPasswordEncoder */ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } } - 
启动测试
访问地址: http://127.0.0.1:9999/test/hello
image-20210120142355279从上图可知,已经跳转到自定义登录界面,输入用户名和密码即可访问,如下
image-20210120144802243至此完成了自定义登录界面
 
2.结果
2.1 实现
- 
简介
从上述案例可知,当访问
/test/hello,会跳转到登录界面,登录后在跳转到/test/hello地址上。但是当直接访问登录界面进行登录时,登录后会直接跳转到
/请求,如下:image-20210120145212543同时当登录失败时,页面还是跳转到登录界面,只不过是地址发生了一点点变换,如下
image-20210120145336138但实际开发过程中,更加希望不管是登录成功还是登录失败,都跳转到开发人员指定的地址或者处理器进行逻辑处理,接下来就来解决这个问题
 - 
准备工作
复制
spring-security-login-page项目,修改名字为spring-security-login-result- 
修改
pom.xml,修改部分如下图image-20210120150557883 删除
.impl文件,让其重新生成将项目设置为
maven项目修改启动类名为
SpringSecurityLoginResultApplication
 - 
具体实现
解决上述问题有两种方式:
- 通过配置登录成功或者失败后的地址处理
 - 通过配置登录成功或者失败后的处理器处理
 
下面就对每种方案进行实现
自定义地址
- 
在
web层增加ResultController,内容如下:package com.briup.security.web; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/result") public class ResultController { /** * 登录成功跳转地址 * @return */ @GetMapping("/success") public String loginSuccess() { return "登录成功"; } /** * 登录失败跳转地址 * @return */ @GetMapping("/fail") public String loginFail() { return "登录失败"; } } - 
修改配置,内容如下:
package com.briup.security.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表示使用表单进行登录 .loginPage("/login.html") // 表示登录界面地址 .loginProcessingUrl("/user/login") //表单登录地址 .successForwardUrl("/result/success") // 登录成功跳转的地址 .failureForwardUrl("/result/fail") // 登录失败跳转的地址 .and() // 拼接 条件 .authorizeRequests() // 设置需要认证的请求地址 .antMatchers("/","/login.html","/user/login").permitAll() // 设置 不需要认证的请求 .anyRequest().authenticated() // 任何请求都需要认证 .and() .csrf().disable(); // 关闭 security 的 csrf防护 } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 设置认证逻辑为用户自定义认证逻辑 * 设置密码加密处理器为 BCryptPasswordEncoder */ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } }image-20210120152921249 - 
启动测试
当输入正确用户名密码时,效果如下:
image-20210120153018296当输入错误的用户名密码,效果如下:
image-20210120153047087 
 
自定义处理器
- 
新建登录成功处理器,内容如下:
package com.briup.security.handler; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; public class LoginSuccessHandler implements AuthenticationSuccessHandler { /** * @param request request对象 * @param response 响应对象 * @param authentication 身份认证对象,通过该对象可以获取用户名 * * */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 获取用户权限列表 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); authorities.forEach(System.out::println); // 获取用户名 String name = authentication.getName(); System.out.println(name); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.print("登录成功"); writer.close(); } }成功处理器必须要实现
AuthenticationSuccessHandler - 
新建登录失败处理器,内容如下:
package com.briup.security.handler; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class LoginFailHandler implements AuthenticationFailureHandler { /** * @param request 请求对象 * @param response 响应对象 * @param exception 校验的异常对象 */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.print("登录失败:" + exception.getMessage()); writer.close(); } }失败处理器必须要实现
AuthenticationFailureHandler - 
修改配置类,内容如下
package com.briup.security.config; import com.briup.security.handler.LoginFailHandler; import com.briup.security.handler.LoginSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表示使用表单进行登录 .loginPage("/login.html") // 表示登录界面地址 .loginProcessingUrl("/user/login") //表单登录地址 //.successForwardUrl("/result/success") // 登录成功跳转的地址 //.failureForwardUrl("/result/fail") // 登录失败跳转的地址 .successHandler(new LoginSuccessHandler()) .failureHandler(new LoginFailHandler()) .and() // 拼接 条件 .authorizeRequests() // 设置需要认证的请求地址 .antMatchers("/","/login.html","/user/login").permitAll() // 设置 不需要认证的请求 .anyRequest().authenticated() // 任何请求都需要认证 .and() .csrf().disable(); // 关闭 security 的 csrf防护 } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 设置认证逻辑为用户自定义认证逻辑 * 设置密码加密处理器为 BCryptPasswordEncoder */ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } }与之前的配置类相比,修改了如下图中内容
image-20210120155256684- 
启动测试
输入正确的用户名密码
[图片上传失败...(image-c8f7a0-1611217444363)]
同时控制台输出内容如下
image-20210120155946228 
 - 
 
  当输入错误用户名和密码时,如下:
  
2.2 对比
- 
获取权限方面
通过地址进行登录的处理,无法获取到到权限
通过处理器进行登录处理,可以获取到用户名
 - 
获取用户名方面
通过地址进行登录处理,直接在方法上注入用户名密码即可
image-20210120163054617通过处理器进行登录处理,则需要借助于
Authentication实例 
3.分离
- 
简介
在前后端分离情况下,登录逻辑与之前的登录逻辑不通,具体如下:
- 后端不需要提供界面,而是提供一个登录接口,登录成功以后产生一个token,返回给前端
 - 前端在之后的请求将产生的token,发送给后端,后端进行校验,校验通过认为登录通过,让其访问具体的资源
 
具体如下图所示:
image-20210120170827558 - 
实现
实现基于
Spring Security的前后端分离登录需要以下几个步骤- 项目准备
 - 增加
swagger - 增加
jwt - 增加自定义响应结构
 - 增加处理逻辑
 
 增加校验逻辑
接下来就挨个实现上述步骤
3.1 项目准备
复制
spring-security-config-account项目,修改名字为spring-security-separate-login- 
修改
pom.xml,修改后变换的内容如下标注image-20210120171635669同时删除
<name>标签中的内容 删除
.impl文件,让其重新生成将复制的内容是其称为一个maven项目
将项目clean
修改启动类名为
SpringSecuritySeparateLoginApplication
3.2 接口文档
swagger作用这里不再解释,可以参考其他资料了解swagger的作用
修改pom.xml,增加swagger配置
<dependency>
  <groupId>com.spring4all</groupId>
  <artifactId>swagger-spring-boot-starter</artifactId>
  <version>1.9.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
修改配置文件application.yml,增加swagger配置
server:
  port: 9999
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://172.16.0.154:3306/test?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
  jpa:
    show-sql: true
swagger:
  base-package: com.briup.security.web
在启动类上加上@EnableSwagger2Doc注解

3.3 JWT配置
jwt作用这里不再解释,可以参考其他资料了解jwt的作用
修改pom.xml,增加jwt依赖
  <dependency>
    <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
  </dependency>
增加jwt工具类,内容如下
package com.briup.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author wangzh
*/
public class JwtUtil {
  /**
   * 过期时间 单位:毫秒
   */
  private static final long EXPIRE_TIME =   30 * 60 * 1000;
  private static final String SECRET = "security_jwt";
  public static final String TOKEN_HEAD = "TOKEN";
  private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
  /**
   * 签发 token
   * @param userId 用户信息
   * @param info 用户自定义信息
   * @return
   */
  public static String sign(String userId, Map<String,Object> info) {
      // 设置过期时间
      Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
      // 设置加密算法
      Algorithm hmac512 = Algorithm.HMAC512(SECRET);
      return JWT.create()
                  .withAudience(userId) // 将用户id放入到token中
                  .withClaim("info",info) // 自定义用户信息
                  .withExpiresAt(date) // 设置过期时间
                  .sign(hmac512);
  }
  /**
   * 从token中获取userId
   * @param token
   * @return
   */
  public static String getUserId(String token) {
      try {
          return JWT.decode(token).getAudience().get(0);
      } catch (JWTDecodeException e) {
          logger.error(e.getMessage());
          return null;
      }
  }
  /**
   * 从token中获取自定义信息
   * @param token
   * @return
   */
  public static Map<String,Object> getInfo(String token) {
      try {
          Claim claim = JWT.decode(token).getClaim("info");
          return claim.asMap();
      } catch (JWTDecodeException e) {
          logger.error(e.getMessage());
          return null;
      }
  }
  /**
   * 校验token
   * @param token
   * @return
   */
  public static boolean checkSign(String token) {
      try {
          Algorithm algorithm = Algorithm.HMAC512(SECRET);
          JWTVerifier verifier = JWT.require(algorithm).build();
          verifier.verify(token);
          return true;
      } catch (Exception e) {
          logger.info("token 无效:" + e.getMessage());
          throw new RuntimeException("token无效,请重新获取");
      }
  }
}
3.4 响应结构
前后端分离中,增加自定义响应结构,这样可以更加规范后端返回给前端的数据样式
修改pom.xml,增加lombok依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
增加自定义响应结构类
package com.briup.security.util;
import lombok.Data;
import lombok.Getter;
@Getter
public class Result<T> {
    /**
     * 业务状态码
     */
    private Integer code;
    /**
     * 状态码信息对应数据
     */
    private String message;
    /**
     * 响应时间
     */
    private Long time;
    /**
     * 响应数据
     */
    private T data;
    private Result(Integer code,String message,T data) {
        this.code = code;
        this.message = message;
        this.time = System.currentTimeMillis();
        this.data = data;
    }
    public static <E> Result<E> success(E data) {
        return new Result<>(200,"成功",data);
    }
    public static  Result success() {
        return success(null);
    }
    public static <E> Result<E> fail(Integer code,String message,E data) {
        return new Result<>(code,message,data);
    }
   public static Result fail(Integer code,String message) {
       return new Result<>(code, message, null);
   }
}
3.5 结果处理
这里演示在处理器 和
url返回token
3.5.1 处理器处理
- 
登录成功以后,在处理器返回
token增加登录controller
package com.briup.security.web; import io.swagger.annotations.Api; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Api(tags = "账户管理") @RequestMapping("/account") public class AccountController { @PostMapping("/login") public void login(String username,String password) { } }注意:这里不要写任何逻辑,
spring security自己会去校验用户名和密码新增成功处理器,内容如下
package com.briup.security.handler; import com.briup.security.util.JwtUtil; import com.briup.security.util.Result; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class SuccessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 登录成功产生token String name = authentication.getName(); String token = JwtUtil.sign(name, null); String result = objectMapper.writeValueAsString(Result.success(token)); response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(result); writer.close(); } }新增失败处理器
package com.briup.security.handler; import com.briup.security.util.Result; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class FailHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String result = objectMapper.writeValueAsString(Result.fail(501, "用户名密码错误")); response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(result); writer.close(); } }修改配置类,内容如下:
package com.briup.security.config; import com.briup.security.handler.FailHandler; import com.briup.security.handler.SuccessHandler; 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Bean public SuccessHandler successHandler() { return new SuccessHandler(); } @Bean public FailHandler failHandler() { return new FailHandler(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/account/page") // 当请求需要认证,跳转到该地址 .loginProcessingUrl("/account/login") // 请求认证地址 .successHandler(successHandler()) .failureHandler(failHandler()) .and() .authorizeRequests() .antMatchers("/account/login","/account/page").permitAll() // 登录请求也不需要认证 .antMatchers( "/webjars/**", "/api/**", "/swagger-ui.html", "/swagger-resources/**", "/v2/**", "/swagger-resources/**").permitAll() // swagger 界面不需要认证 .anyRequest().authenticated() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 设置认证逻辑为用户自定义认证逻辑 * 设置密码加密处理器为 BCryptPasswordEncoder */ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } }启动测试
image-20210121145252135访问登录请求,可以看到返回token地址
image-20210121145342810同时用户名输入错误,也可以看到对应的结果
image-20210121145414947访问其他请求,发现也访问不了
image-20210121145501945至此,完成了登录成功后在处理器返回token
 
3.5.2 URL处理
登录成功后通过
url返回token
增加登录成功或者失败以后的接口
package com.briup.security.web;
import com.briup.security.util.JwtUtil;
import com.briup.security.util.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api(tags = "账户管理")
@RequestMapping("/account")
public class AccountController {
    @PostMapping("/login")
    public void login(String username,String password) {
    }
    @GetMapping("/page")
    public Result<String> page() {
        return Result.fail(401,"该请求需要认证");
    }
    @PostMapping("/success")
    public Result<String> success(String username) {
        return Result.success(JwtUtil.sign(username,null));
    }
    @PostMapping("/fail")
    public Result<String> fail(String username) {
        return Result.fail(401,"用户名密码错误");
    }
}
修改配置类,内容如下
package com.briup.security.config;
import com.briup.security.handler.FailHandler;
import com.briup.security.handler.SuccessHandler;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("myDetailService")
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Bean
    public SuccessHandler successHandler() {
        return new SuccessHandler();
    }
    @Bean
    public FailHandler failHandler() {
        return new FailHandler();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/account/page") // 当请求需要认证,跳转到该地址
            .loginProcessingUrl("/account/login") // 请求认证地址
           // .successHandler(successHandler())
           // .failureHandler(failHandler())
            .successForwardUrl("/account/success")
            .failureForwardUrl("/account/fail")
            .and()
            .authorizeRequests()
            .antMatchers("/account/login","/account/page").permitAll() // 登录请求也不需要认证
            .antMatchers(
                        "/webjars/**",
                        "/api/**",
                        "/swagger-ui.html",
                        "/swagger-resources/**",
                        "/v2/**",
                        "/swagger-resources/**").permitAll() // swagger 界面不需要认证
            .anyRequest().authenticated()
            .and().csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 设置认证逻辑为用户自定义认证逻辑
         * 设置密码加密处理器为 BCryptPasswordEncoder
         */
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
}
效果与之前效果一样,这里就不做演示了
3.6 TOKEN校验
认证完成,则需要校验token,校验token其原理很简单,具体如下:
- 在
UsernamePasswordAuthenticationFilter过滤器执行之前增加一个自定义过滤器 - 自定义过滤器就是用来校验
token,token合法则将请求转发给下一个过滤器 
接下来就来实现上述过程,具体如下
- 
自定义过滤器
package com.briup.security.filter; import com.briup.security.util.JwtUtil; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class AuthenticationTokenFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(AuthenticationTokenFilter.class); @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private ObjectMapper objectMapper; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 1.从请求头中获取Token String token = request.getHeader(JwtUtil.TOKEN_HEAD); //2.判断token是否为空,则请求放心,让UsernamePasswordAuthenticationFilter校验用户名密码 if (token == null || "".equals(token)) { filterChain.doFilter(request,response); return; } try { //3.如果token不为空,则去校验token, if (JwtUtil.checkSign(token)) { // 获取用户信息 String userId = JwtUtil.getUserId(token); UserDetails userDetails = userDetailsService.loadUserByUsername(userId); /** * UsernamePasswordAuthenticationToken * 这个对象使用来保存用户信息 * 如果SecurityContextHolder.getContext()中有该对象,那么就不需要再次校验 */ UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } catch (Exception e) { e.printStackTrace(); logger.info("校验用户名密码失败"); } } } - 
修改配置类,将上述过滤器添加到
UsernamePasswordAuthenticationFilter前面package com.briup.security.config; import com.briup.security.filter.AuthenticationTokenFilter; import com.briup.security.handler.FailHandler; import com.briup.security.handler.SuccessHandler; 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myDetailService") private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Bean public SuccessHandler successHandler() { return new SuccessHandler(); } @Bean public FailHandler failHandler() { return new FailHandler(); } @Bean public AuthenticationTokenFilter authenticationTokenFilter() { return new AuthenticationTokenFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); // 添加过滤器到 UsernamePasswordAuthenticationFilter前面 http.formLogin() .loginPage("/account/page") // 当请求需要认证,跳转到该地址 .loginProcessingUrl("/account/login") // 请求认证地址 .successHandler(successHandler()) .failureHandler(failHandler()) // .successForwardUrl("/account/success") // .failureForwardUrl("/account/fail") .and() .authorizeRequests() .antMatchers("/account/login","/account/page").permitAll() // 登录请求也不需要认证 .antMatchers( "/webjars/**", "/api/**", "/swagger-ui.html", "/swagger-resources/**", "/v2/**", "/swagger-resources/**").permitAll() // swagger 界面不需要认证 .anyRequest().authenticated() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 设置认证逻辑为用户自定义认证逻辑 * 设置密码加密处理器为 BCryptPasswordEncoder */ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } } - 
启动测试
进行登录产生
tokenimage-20210121160944699将token添加到
swagger认证中image-20210121161013058访问
test/hello请求image-20210121161036582说明过滤器生效,且校验通过
 






















