前端代码
本次采用简单的html静态页面作为演示,也可结合vue前后端分离开发,复制就可运行测试
项目目录
登录界面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function refresh() {document.getElementById('captcha_img').src="/kaptcha?"+Math.random();}</script>
</head>
<body>
<form action="/login" method="post">账号:<input type="text" placeholder="请输入账号" name="username"><br>密码:<input type="password" placeholder="请输入密码" name="password"><br>验证码: <input type="text" placeholder="请输入验证码" name="code"><div class="item-input"><img id="captcha_img" alt="点击更换" title="点击更换"onclick="refresh()" src="/kaptcha" /></div>记住我:<input type="checkbox" name="remember-me" value="true"><br><input type="submit" value="提交"/>
</form>
</body>
</html>
登录成功
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
成功<a href="/logout">退出</a>
</body>
</html>
后端代码
pom
<dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--图形验证码--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><!--security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><!--数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency></dependencies>
配置类
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 {@Beanpublic 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 {@Autowiredprivate DataSource dataSource;@AutowiredPersistentTokenRepository persistentTokenRepository;@Beanpublic PasswordEncoder getPw(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 基于内存存储的多用户auth.inMemoryAuthentication().withUser("admin").password(getPw().encode("123")).roles("root");}@Overridepublic void configure(WebSecurity web) throws Exception {// 忽略静态请求web.ignoring().antMatchers("/img/**", "/js/**");}@Overrideprotected 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);}@Beanpublic AuthenticationManager authenticationManagerBean()throws Exception {return super.authenticationManagerBean();}@Beanpublic PersistentTokenRepository getPersistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//第一次启动时建表,第二次使用时注释掉//jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}
}
controller
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 {@AutowiredDefaultKaptcha 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();// 验证码放入sessionsession.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";}}
filter
用于检测图像验证码的正确性,只有当验证码正确时,过滤器链才会走到springsecurity的检测
public class CodeFilter extends HttpFilter {@Overrideprotected 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%2B8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rootmain:allow-circular-references: true #开始支持spring循环依赖
当第一次执行项目时,会在库中生成表数据