根據隨機數生成圖片
將隨機數存到session中
在將生成的圖片寫到接口的響應
1. 創建項目
使用idea
中的spring
工具引入springboot
和springsecruity
創建項目最終的pom.xml
如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-spring-secruity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-spring-secruity</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
創建一個基本訪問路由
package com.example.demospringsecruity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 簡單測試控制器
* @author john
* @date 2020/1/6 - 9:47
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello spring secruity";
}
}
創建一個簡單的響應對象
package com.example.demospringsecruity.support;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 一個簡單的響應對象
* @author john
* @date 2020/1/6 - 16:23
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SimpleResponse {
private Object content;
}
實現圖形驗證碼的校驗,需要在用戶和密碼認證過濾器前增加一個驗證碼過濾器處理判斷邏輯
2. 創建驗證碼類
package com.example.demospringsecruity.utils;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
* 驗證碼對象
*
* @author john
* @date 2020/1/7 - 9:54
*/
@Data
@ToString
@NoArgsConstructor
public class ImageCode {
//驗證碼
private String code;
//過期時間
private LocalDateTime expireTime;
//圖片
private BufferedImage image;
public ImageCode(String code, int expireTime, BufferedImage image) {
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
this.image = image;
}
public boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);
}
}
3. 編寫驗證碼的過濾器
package com.example.demospringsecruity.filter;
import com.example.demospringsecruity.controller.ValidateCodeController;
import com.example.demospringsecruity.exception.ValidateCodeException;
import com.example.demospringsecruity.handler.MyAuthenticationFailureHandler;
import com.example.demospringsecruity.utils.ImageCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
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;
/**
* @author john
* @date 2020/1/7 - 10:41
*/
@Slf4j
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
public MyAuthenticationFailureHandler getMyAuthenticationFailureHandler() {
return myAuthenticationFailureHandler;
}
public void setMyAuthenticationFailureHandler(MyAuthenticationFailureHandler myAuthenticationFailureHandler) {
this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;
}
/**
* 操作session的工具類
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
//只有當處理登錄功能時才執行
if (StringUtils.equals("/auth/form", request.getRequestURI()) && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
// 最好做try catch 處理異常
try {
validate(new ServletWebRequest(request));
} catch (AuthenticationException e) {
myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
filterChain.doFilter(request, response);
}
//執行驗證碼邏輯
private void validate(ServletWebRequest request) throws AuthenticationException, ServletRequestBindingException {
//取出session中的驗證碼對象
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
//取出表單提交中的驗證碼信息
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("code in request is null");
}
if (codeInSession == null) {
throw new ValidateCodeException("code in session is null");
}
if (codeInSession.isExpried()) {
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("code has expreied");
}
if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException("code is not right");
}
//驗證通過,清除session中的驗證碼信息
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
}
}
4. 定義一個自定義登錄錯誤處理
package com.example.demospringsecruity.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定義登錄失敗處理
*
* @author john
* @date 2020/1/6 - 18:43
*/
@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
log.info("登錄失敗");
//這里處理登錄失敗后就會輸出錯誤信息
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset-UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));
}
}
5. 自定義驗證碼異常
package com.example.demospringsecruity.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 自定義異常
* @author john
* @date 2020/1/7 - 11:25
*/
public class ValidateCodeException extends AuthenticationException {
public ValidateCodeException(String msg) {
super(msg);
}
}
6. 定義驗證碼圖片輸出控制器
package com.example.demospringsecruity.controller;
import com.example.demospringsecruity.utils.ImageCode;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
/**
* 驗證碼校驗控制器
*
* @author john
* @date 2020/1/7 - 9:57
*/
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = createImageCode(request);
//將驗證碼存入session
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
//輸出圖片
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
//創建驗證碼對象
private ImageCode createImageCode(HttpServletRequest request) {
int width = 67;
int height = 23;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
String sRand = "";
for (int i = 0; i < 4; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(sRand, 60, image);
}
/**
* 生成隨機背景條紋
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
7. 配置登錄邏輯以及添加自定義的驗證碼過濾器到用戶密碼過濾器前面
package com.example.demospringsecruity.config;
import com.example.demospringsecruity.filter.ValidateCodeFilter;
import com.example.demospringsecruity.handler.MyAuthenticationFailureHandler;
import org.springframework.beans.factory.annotation.Autowired;
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.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.stereotype.Component;
import javax.servlet.Filter;
/**
* @author john
* @date 2020/1/6 - 10:07
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
ValidateCodeFilter validateCodeFilter;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
//手動將PasswordEncoder注入到ioc容器中
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
validateCodeFilter.setMyAuthenticationFailureHandler(myAuthenticationFailureHandler);
// 表單登錄
http //過濾器設置
// 將驗證碼過濾器配置到UsernamePasswordAuthenticationFilter前面
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
//登錄設置
.formLogin()
.loginPage("/signin.html") //設置登錄路由
.loginProcessingUrl("/auth/form") //設置登錄處理url
.failureHandler(myAuthenticationFailureHandler)
.and()
// 身份認證設置
.authorizeRequests()
.antMatchers("/signin.html").permitAll() //該路由不需要身份認賬
.antMatchers("/code/*").permitAll() //該路由不需要身份認賬
.anyRequest() //其他的路由均需要身份認證
.authenticated()
.and()
//先禁用防止跨站腳本攻擊的csrf token
.csrf()
.disable();
}
}
8. 自定義用戶認證
package com.example.demospringsecruity.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* @author john
* @date 2020/1/6 - 10:32
*/
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
//這里可以注入mapper或者repository的dao對象來實現數據校驗邏輯操作
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("用戶名:" + username);
//這里密碼應該從數據庫中取出,暫時先使用加密生成
String password = passwordEncoder.encode("123456");
return new User(username,
password,
true, // 賬戶是否可用
true, // 賬戶是否過期
true, // 密碼是否過期
true, //賬戶是否被鎖定
AuthorityUtils.commaSeparatedStringToAuthorityList("admin") //授權集合
);
}
}
9. 靜態登錄頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
</head>
<body>
<h2>標准登錄頁面</h2>
<h3>表單登錄</h3>
<form action="/auth/form" method="post">
<table>
<tr>
<td>用戶名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>圖形驗證碼</td>
<td><input type="text" name="imageCode"><img src="/code/image" alt="圖形驗證碼"></td>
</tr>
<tr>
<td colspan="2">
<button type="submit">登錄</button>
</td>
</tr>
</table>
</form>
</body>
</html>
10. 測試
驗證碼錯誤結果
驗證碼正確,用戶密碼校驗通過結果
11. 代碼資源
鏈接:https://share.weiyun.com/5jKnFRj 密碼:5vvs5x