本次采用简单的html静态页面作为演示,也可结合vue前后端分离开发,复制就可运行测试
项目目录
登录界面
Title
登录成功
Title
成功退出
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.github.penggle
kaptcha
2.3.2
org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-test
test
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-jdbc
kaptcha配置类用于生成验证码格式
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfiguration {
@Bean
public DefaultKaptcha getDefaultKaptcha() {
com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
Properties properties = new Properties();
properties.put("kaptcha.textproducer.char.string", "0123456789");
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "5");
properties.put("kaptcha.textproducer.char.length","4");
properties.put("kaptcha.image.height","34");
properties.put("kaptcha.textproducer.font.size","30");
properties.setProperty("kaptcha.image.width", "164");
properties.setProperty("kaptcha.image.height", "64");
properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
springsecurity
import com.sfy.kapcha.filter.CodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
PersistentTokenRepository persistentTokenRepository;
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 基于内存存储的多用户
auth.inMemoryAuthentication().withUser("admin").password(getPw().encode("123")).roles("root");
}
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略静态请求
web.ignoring().antMatchers("/img/**", "/js/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//当发现是login时认为是登录,必须和表单提供的地址一致去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录界面
.loginPage("/login.html")
.successForwardUrl("/toMain")
.permitAll()
.and()
.addFilterBefore(new CodeFilter(), UsernamePasswordAuthenticationFilter.class);
//认证授权
http.authorizeRequests()
.antMatchers("/kaptcha").permitAll()
//登录放行不需要认证
.antMatchers("/login.html").permitAll()
//所有请求都被拦截类似于mvc必须登录后访问
.anyRequest().authenticated();
//关闭csrf防护
http.csrf().disable();
//退出登录
http.logout()
.logoutSuccessUrl("/login.html");
//记住我
http.rememberMe().tokenValiditySeconds(60);
}
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//第一次启动时建表,第二次使用时注释掉
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
kaptcha
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
/**
* @Author: sfy
* @Date: 2024/1/18 11:13
*/
@RestController
public class KapchaController {
@Autowired
DefaultKaptcha defaultKaptcha;
@GetMapping("/kaptcha")
public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpSession session = request.getSession();
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 capText = defaultKaptcha.createText();
// 验证码放入session
session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
BufferedImage bi = defaultKaptcha.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
}
}
login进行简单的页面重定向(要用Controller)
@Controller
public class LoginController {
@RequestMapping("/toMain")
public String toMain(){
return "redirect:main.html";
}
}
用于检测图像验证码的正确性,只有当验证码正确时,过滤器链才会走到springsecurity的检测
public class CodeFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String uri = req.getServletPath();
if (uri.equals("/login") && req.getMethod().equalsIgnoreCase("post")) {
// 服务端生成的验证码数据
String sessionCode = req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY).toString();
System.out.println("正确的验证码: " + sessionCode);
// 用户输入的验证码数据
String formCode = req.getParameter("code").trim();
System.out.println("用户输入的验证码: " + formCode);
if (StringUtils.isEmpty(formCode)) {
throw new RuntimeException("验证码不能为空");
}
if (sessionCode.equals(formCode)) {
System.out.println("验证通过");
} else {
throw new AuthenticationServiceException("验证码输入不正确");
}
}
chain.doFilter(req, res);
}
}
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/security?serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
main:
allow-circular-references: true #开始支持spring循环依赖
当第一次执行项目时,会在库中生成表数据