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
文件,修改的内容如下 -
测试
访问地址:http://127.0.0.1:9999/hello/test
跳转到默认提供的登录界面
-
自定义登录界面
要想实现自定义登录界面需要以下两步:
- 撰写登录界面
- 修改
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
获取,如下同时还限制了其请求方式为
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
从上图可知,已经跳转到自定义登录界面,输入用户名和密码即可访问,如下
至此完成了自定义登录界面
2.结果
2.1 实现
-
简介
从上述案例可知,当访问
/test/hello
,会跳转到登录界面,登录后在跳转到/test/hello
地址上。但是当直接访问登录界面进行登录时,登录后会直接跳转到
/
请求,如下:同时当登录失败时,页面还是跳转到登录界面,只不过是地址发生了一点点变换,如下
但实际开发过程中,更加希望不管是登录成功还是登录失败,都跳转到开发人员指定的地址或者处理器进行逻辑处理,接下来就来解决这个问题
-
准备工作
复制
spring-security-login-page
项目,修改名字为spring-security-login-result
-
修改
pom.xml
,修改部分如下图 删除
.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); } }
-
启动测试
当输入正确用户名密码时,效果如下:
当输入错误的用户名密码,效果如下:
自定义处理器
-
新建登录成功处理器,内容如下:
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-c8f7a0-1611217444363)]
同时控制台输出内容如下
-
当输入错误用户名和密码时,如下:
![image-20210120160032809](https://upload-images.jianshu.io/upload_images/18110702-2ee34bf6afb24d1e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2.2 对比
-
获取权限方面
通过地址进行登录的处理,无法获取到到权限
通过处理器进行登录处理,可以获取到用户名
-
获取用户名方面
通过地址进行登录处理,直接在方法上注入用户名密码即可
通过处理器进行登录处理,则需要借助于
Authentication
实例
3.分离
-
简介
在前后端分离情况下,登录逻辑与之前的登录逻辑不通,具体如下:
- 后端不需要提供界面,而是提供一个登录接口,登录成功以后产生一个token,返回给前端
- 前端在之后的请求将产生的token,发送给后端,后端进行校验,校验通过认为登录通过,让其访问具体的资源
具体如下图所示:
-
实现
实现基于
Spring Security
的前后端分离登录需要以下几个步骤- 项目准备
- 增加
swagger
- 增加
jwt
- 增加自定义响应结构
- 增加处理逻辑
增加校验逻辑
接下来就挨个实现上述步骤
3.1 项目准备
复制
spring-security-config-account
项目,修改名字为spring-security-separate-login
-
修改
pom.xml
,修改后变换的内容如下标注同时删除
<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); } }
启动测试
访问登录请求,可以看到返回token地址
同时用户名输入错误,也可以看到对应的结果
访问其他请求,发现也访问不了
至此,完成了登录成功后在处理器返回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); } }
-
启动测试
进行登录产生
token
将token添加到
swagger
认证中访问
test/hello
请求说明过滤器生效,且校验通过