自定义认证
自定义认证
自定义资源权限
- 公共资源: 可以直接访问
- 受保护资源: 需要权限管理(认证授权之后才能访问),默认所有资源都是受保护的,需要认证授权之后才能访问
注意
- permitAll() 代表放行该资源,该资源为公共资源,无需认证和授权可以直接访问
- anyRequest().authenticated() 代表所有请求,必须认证之后才能访问
- formLogin() 代表开启表单认证
注意: 放行资源必须放在所有认证请求之前!
当系统中不存在WebSecurityConfigurerAdapter和SecurityFilterChain实例时,Spring Security会使用默认的配置。
当存在其中任何一个时,默认的配置就会失效。
Spring Security 2.6.2及以前版本
写法一:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启认证
.authorizeHttpRequests()
// 对于部分请求方形
.mvcMatchers("/order/test1","/order/test2","/hello/test2","/hello/test3").permitAll()
// 其余请求全部需要认证
.anyRequest().authenticated()
// 链式编程写法
.and()
// 表单登录
.formLogin();
}
}写法二:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启认证
.authorizeHttpRequests()
// 对于部分请求方形
.mvcMatchers("/order/test1").permitAll()
.mvcMatchers("/order/test2").permitAll()
.mvcMatchers("/hello/test2").permitAll()
.mvcMatchers("/hello/test3").permitAll()
// 其余请求全部需要认证
.anyRequest().authenticated();
// 表单登录
http.formLogin();
}
}新版Security
在SpringBoot 2.7.1 中的spring-security-config-5.7.2.RELEASE中已提到WebSecurityConfigurerAdapter已过时被弃用,替代方法如下:
使用 SecurityFilterChain Bean 来配置 HttpSecurity;
使用 WebSecurityCustomizer Bean 来配置 WebSecurity。
参考链接:
SpringSecurity - WebSecurityConfigurerAdapter 过时问题_爱上云原生的博客-CSDN博客
Spring Security即将弃用配置类WebSecurityConfigurerAdapter_码农小胖哥的博客-CSDN博客
替代后新代码为:
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.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity //添加security过滤器,此处一定要加入此注解,否则下面的httpSecurity无法装配
public class WebSecurityConfig {
/**
* 说明: SpringBoot2.7.1版本
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests()
.mvcMatchers("/index").permitAll()
.anyRequest().authenticated()
.and().formLogin().and().build();
}
}自定义登录界面
引入Thymleaf
引入Thymleaf的依赖
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
</dependency>并在application.properties添加配置信息
#thymeleaf 部分配置示例
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.mode=HTML编写Controller
@Controller
public class LoginController {
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
}@Controller
public class IndexController {
@RequestMapping("/index")
public String index(){
return "index";
}
}编写登录页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
.login_main {
margin: 7em auto;
width: 24%;
height: 30em;
text-align: center;
}
.login_info {
font-size: 2em;
}
.login_textbox {
width: 52%;
height: 0.5em;
margin: 2% 0;
padding: 4%;
color: gray;
background-color: #f0f0f0;
border: none;
border-radius: 0.2em;
}
.login_btn {
width: 50%;
height: 3em;
background-color: #FFE4E1;
margin: 2% 0;
}
</style>
<body>
<form class="login_main" method="post" th:action="@{/doLogin}">
<p class="login_info">登录</p>
<input class="login_textbox" type="text" name="uname" placeholder="用户名" required th:value="root">
<input class="login_textbox" type="password" name="pwd" placeholder="密码" required th:value="root">
<button class="login_btn" type="submit">sign in</button>
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
</form>
</body>
</html>注意:
- 登录表单提交方法必须是Post
- 表单提交的参数名默认为username和password,如果不一致,需要与后台配合设置
编写Security配置类
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
//开放登录跳转请求
.mvcMatchers("/toLogin").permitAll()
.mvcMatchers("/order/test1","/order/test2","/hello/test2","/hello/test3").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
// 指定跳转登录页面的请求路径。 注意: 一旦自定义登录页面后,必须指定登录处理的请求url
.loginPage("/toLogin")
// 指定处理登录的请求url
.loginProcessingUrl("/doLogin")
// 指定登录页面用户名参数的名称
.usernameParameter("uname")
// 指定登录页面密码参数的名称
.passwordParameter("pwd")
// 认证成功后 forward跳转 注意: 不会跳转到之前的请求路径
//.successForwardUrl("/index")
//认证成功后 redirect重定向 注意: 如果之前请求被拦截到登录页,认证成功后会自动跳转会原来的请求
.defaultSuccessUrl("/index")
//.defaultSuccessUrl("/index", true) //第二个参数设置为true时总是跳转,效果同successForwardUrl一致,默认false
.failureUrl("/toLogin") //登录失败后跳转路径
.and()
// 禁用cors 跨站请求保护
.cors().disable();
}
}注意点:
loginPage() 方法用来指定跳转登录页面的请求,但是注意两点:
- 一旦自定义了登录页面以后,必须通过loginProcessingUrl()方法指定登录请求处理的url
- 必须将跳转登录页面请求的路径开放
如果前端自定义了参数,必须在配置类中进行配置
successForwardUrl() 和 defaultSuccessUrl()
successForwardUrl():
不会跳转到拦截之前的请求,而是直接跳转到当前配置的页面defaultForwardUrl():
如果之前是某个请求被拦截,登录后会默认跳转回之前的请求;如果是直接请求登录页, 则跳转到配置的默认跳转页面;
可以通过第二个参数defaultForwardUrl(url, true)实现与successForwardUrl相同的功能;

自定义登录成功处理
有时候页面跳转并不能满足我们,特别是在前后端分离开发中就不需要成功之后跳转页面,只需要给前端返回一个 JSON数据通知登录成功还是失败与否,这个时候可以通过自定义 AuthenticationSucccessHandler 实现。
AuthenticationSucccessHandler 接口定义
public interface AuthenticationSuccessHandler {
/**
* Called when a user has been successfully authenticated.
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during
* the authentication process.
*/
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException;
}根据接口的描述信息也可以得知,登录成功后会自动回调这个方法,进一步查看它的默认实现,你会发现successForwardUrl、defaultSuccessUrl也是由它的子类实现的:

在config包中自定义 AuthenticationSuccessHandler接口实现类MyAuthenticationSuccessHandler
import com.fasterxml.jackson.databind.ObjectMapper;
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.util.HashMap;
import java.util.Map;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> result = new HashMap<String, Object>();
result.put("msg", "登录成功");//打印登录成功信息
result.put("status", 200);//打印状态码
result.put("authentication", authentication);//打印认证信息
response.setContentType("application/json;charset=UTF-8");//设置响应类型
String s = new ObjectMapper().writeValueAsString(result);//json格式转字符串
response.getWriter().println(s);//打印json格式数据
}
}在WebSecurityConfigurer中配置MyAuthenticationSuccessHandler
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
......
//.successForwardUrl("/index") //认证成功后跳转路径 forward 跳转路径 始终在认证成功之后跳转到指定请求 地址栏不变
//.defaultSuccessUrl("/hello") //默认认证成功后跳转路径 redirect 重定向 注意:如果之前有请求过的路径,会优先跳转之前的请求路径 地址栏改变
//.defaultSuccessUrl("/hello",true) //第二个参数设置为true时总是跳转,效果同successForwardUrl一致,默认false
.successHandler(new MyAuthenticationSuccessHandler())//认证成功时处理,前后端分离解决方案
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}访问路径:http://localhost:8080/hello,输入用户名密码登录后,输出结果

显示登录失败信息
为了能更直观地在登录页面看到异常错误信息,可以在登录页面中直接获取异常信息。Spring Security 在登录失败之后会将异常信息存储到 request 、session作用域中key为 SPRING_SECURITY_LAST_EXCEPTION 命名属性中,源码可以参考 SimpleUrlAuthenticationFailureHandler :
SimpleUrlAuthenticationFailureHandler 源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.security.web.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private String defaultFailureUrl;
private boolean forwardToDestination = false;
private boolean allowSessionCreation = true;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public SimpleUrlAuthenticationFailureHandler() {
}
public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
this.setDefaultFailureUrl(defaultFailureUrl);
}
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (this.defaultFailureUrl == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Sending 401 Unauthorized error since no failure URL is set");
} else {
this.logger.debug("Sending 401 Unauthorized error");
}
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
} else {
this.saveException(request, exception);
if (this.forwardToDestination) {
this.logger.debug("Forwarding to " + this.defaultFailureUrl);
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
} else {
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
}
protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
if (this.forwardToDestination) {
request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
} else {
HttpSession session = request.getSession(false);
if (session != null || this.allowSessionCreation) {
request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
}
}
}
public void setDefaultFailureUrl(String defaultFailureUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl), () -> {
return "'" + defaultFailureUrl + "' is not a valid redirect URL";
});
this.defaultFailureUrl = defaultFailureUrl;
}
protected boolean isUseForward() {
return this.forwardToDestination;
}
public void setUseForward(boolean forwardToDestination) {
this.forwardToDestination = forwardToDestination;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
return this.redirectStrategy;
}
protected boolean isAllowSessionCreation() {
return this.allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
}
login.html中添加显示异常信息代码
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
...
<h2>
<!-- <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>--><!--request作用域:forward跳转-->
<div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></div><!--session作用域:redirect跳转-->
</h2>
</body>
</html>WebSecurityConfigurer配置登录失败处理
package com.study.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
.successHandler(new MyAuthenticationSuccessHandler())//认证成功时处理,前后端分离解决方案
//.failureForwardUrl("/login.html")//认证失败之后,forward跳转
.failureUrl("/login.html") //默认认证失败之后,redirect跳转
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}注意
failureUrl、failureForwardUrl 关系类似于之前提到的 successForwardUrl 、defaultSuccessUrl 方法
failureUrl:失败以后的重定向跳转
failureForwardUrl:失败以后的 forward 跳转
因此获取 request 中异常信息,这里只能使用failureForwardUrl
测试
(1)failureForwardUrl:forward跳转,request作用域

(2)failureUrl:redirect跳转,session作用域

自定义登录失败处理
和自定义登录成功处理一样,Spring Security 同样为前后端分离开发提供了登录失败的处理,这个类就是 AuthenticationFailureHandler
源码
public interface AuthenticationFailureHandler {
/**
* Called when an authentication attempt fails.
* @param request the request during which the authentication attempt occurred.
* @param response the response.
* @param exception the exception which was thrown to reject the authentication
* request.
*/
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException;
}根据接口的描述信息,也可以得知登录失败会自动回调这个方法,进一步查看它的默认实现,你会发现failureUrl、failureForwardUrl也是由它的子类实现的。

自定义AuthenticationFailureHandler接口实现类
config包中自定义 AuthenticationFailureHandler接口实现类MyAuthenticationFailureHandler
package com.study.config;
import com.fasterxml.jackson.databind.ObjectMapper;
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.util.HashMap;
import java.util.Map;
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Map<String, Object> result = new HashMap<String, Object>();
result.put("msg", "登录失败:" + exception.getMessage());
result.put("status", 500);
response.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
response.getWriter().println(s);
}
}修改WebSecurityConfigurer配置
WebSecurityConfigurer配置MyAuthenticationFailureHandler
package com.study.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
//.failureForwardUrl("/login.html")//认证失败之后,forward跳转
//.failureUrl("/login.html") //默认认证失败之后,redirect跳转
.failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}测试
测试路径:http://localhost:8080/hello,输入错误的用户名或密码后输出如下结果:

注销登录
Spring Security 中也提供了默认的注销登录配置,在开发时也可以按照自己需求对注销进行个性化定制。
开启注销登录(默认开启)
package com.study.config;
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.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
.failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
.and()
.logout()
.logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
.invalidateHttpSession(true)//默认开启会话失效
.clearAuthentication(true)//默认清除认证标志
.logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}相关信息
通过 logout() 方法开启注销配置
logoutUrl 指定退出登录请求地址,默认是 GET 请求,路径为 /logout
invalidateHttpSession 退出时是否使session 失效,默认值为 true
clearAuthentication 退出时是否清除认证信息,默认值为 true
logoutSuccessUrl 退出登录时跳转地址
测试注销路径:登录成功后,访问:http://localhost:8080/hello,通过访问:http://localhost:8080/logout,进行注销登录。
配置多个注销登录请求
如果项目中有需要,开发者还可以配置多个注销登录的请求,同时还可以指定请求的方法:
package com.study.config;
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.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
.failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
.and()
.logout()
//.logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/aaa", "GET"),
new AntPathRequestMatcher("/bbb", "POST")
))
.invalidateHttpSession(true)//默认开启会话失效
.clearAuthentication(true)//默认清除认证标志
.logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}前后端分离注销登录配置
如果是前后端分离开发,注销成功之后就不需要页面跳转了,只需要将注销成功的信息返回前端即可,此时我们可以通过自定义 LogoutSuccessHandler 实现来返回注销之后信息:
编写LogoutSuccessHandler接口实现类MyLogoutSuccessHandler
package com.study.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> result = new HashMap<String, Object>();
result.put("msg", "注销成功,当前认证对象为:" + authentication);//打印认证信息
result.put("status", 200);//打印状态码
response.setContentType("application/json;charset=UTF-8");//设置响应类型
String s = new ObjectMapper().writeValueAsString(result);//json格式转字符串
response.getWriter().println(s);//打印json格式数据
}
}WebSecurityConfigurer配置MyLogoutSuccessHandler
package com.study.config;
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.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
.failureHandler(new MyAuthenticationFailureHandler())//认证失败时处理,前后端解决方案
.and()
.logout()
//.logoutUrl("/logout")//指定注销登录URL,默认请求方式必须为GET
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/aaa", "GET"),
new AntPathRequestMatcher("/bbb", "POST")
))
.invalidateHttpSession(true)//默认开启会话失效
.clearAuthentication(true)//默认清除认证标志
//.logoutSuccessUrl("/login.html")//注销登录成功后跳转的页面
.logoutSuccessHandler(new MyLogoutSuccessHandler())
.and()
.csrf().disable();//此处先关闭CSRF跨站保护
}
}测试
测试路径:http://localhost:8080/aaa

测试路径:http://localhost:8080/logout.html

