背景
- 通过重写
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
实现自定义登陆 - 发现无论如何登陆, 在访问受保护资源时依然跳转到登陆页
解决
myFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
参考
江南一点雨
完整代码
package com.example.springsecuritytest;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.DelegatingSecurityContextRepository;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.AntPathMatcher;
import java.io.IOException;
import java.util.Properties;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
UserDetails abc = User.withUsername("abc").password("{noop}123").build();
inMemoryUserDetailsManager.createUser(abc);
AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(inMemoryUserDetailsManager);
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
MyFilter myFilter = new MyFilter();
myFilter.setAuthenticationManager(authenticationManager);
myFilter.setPostOnly(true);
myFilter.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/dologin", "POST")
);
myFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
httpSecurity.authorizeHttpRequests(f ->
f
.requestMatchers("/login").permitAll()
.requestMatchers("/vc").permitAll()
.requestMatchers("/dologin").permitAll()
.anyRequest().authenticated()
);
httpSecurity.formLogin(f ->
f
.loginPage("/login")
.loginProcessingUrl("/dologin")
);
httpSecurity.addFilterAt(myFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.authenticationManager(authenticationManager);
httpSecurity.csrf(AbstractHttpConfigurer::disable);
return httpSecurity.build();
}
@Bean
public DefaultKaptcha getDefaultKaptcha(){
DefaultKaptcha captchaProducer = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes");
properties.setProperty("kaptcha.border.color", "20,15,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "110");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "5");
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
captchaProducer.setConfig(config);
return captchaProducer;
}
}
package com.example.springsecuritytest;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.google.code.kaptcha.impl.DefaultKaptcha;
@Controller
public class TestController {
@Autowired
DefaultKaptcha defaultKaptcha;
@RequestMapping("/test")
public String s(){
return "Hello";
}
@RequestMapping("/index")
public String ss(){
return "index";
}
@RequestMapping("/login")
public String ass(){
return "login";
}
@RequestMapping("/vc")
public void fasf(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
String text = defaultKaptcha.createText();
BufferedImage image = defaultKaptcha.createImage(text);
HttpSession session = request.getSession();
session.setAttribute("vc",text);
System.out.println(text);
ServletOutputStream outputStream = response.getOutputStream();
ImageIO.write(image,"jpeg",outputStream);
}
}
package com.example.springsecuritytest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.thymeleaf.util.StringUtils;
public class MyFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
HttpSession session = request.getSession();
String vc = (String) session.getAttribute("vc");
String vc1 = request.getParameter("vc");
if(!StringUtils.equals(vc,vc1)){
System.out.println("验证码不匹配");
throw new BadCredentialsException("验证码不匹配");
}
System.out.println("验证码匹配");
return super.attemptAuthentication(request, response);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form class="form-signin" method="post" action="/dologin">
<h2 class="form-signin-heading">Please sign in</h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required="">
</p>
<p>
<label for="vc" class="sr-only">Password</label>
<input type="text" id="vc" name="vc" class="form-control" placeholder="vc" required="">
</p>
<img src="/vc">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</body>
</html>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>