1. 依賴管理
```xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
```
2. 配置文件`application.yml`
```yaml
server:
port: 80
```
3. 登錄頁`resources/static/login.html`
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<form action="/login" method="post">
<div>
<h3>賬戶登錄</h3>
<input type="text" placeholder="用戶名" name="username" required="required"/>
<input type="password" placeholder="密碼" name="password" required="required"/><br>
<input type="text" name="imageCode" placeholder="驗證碼"/>
<img src="/code/image"/>
<button type="submit">登錄</button>
</div>
</form>
</body>
</html>
```
4. 啟動類
```java
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
```
5. 控制器
- 主頁(可作為資源)
```java
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "hello spring security";
}
}
```
- 獲取驗證碼圖片(暫時存儲在session中)
```java
@RestController
public class ValidateController {
public final static String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 生成隨機碼
String code = createCode(4);
// TODO 將隨機碼存儲起來(為了方便起見,暫時存儲在session中,也可存在關系型或非關系型數據庫中)
request.getSession().setAttribute(SESSION_KEY, code);
// 將圖片傳給用戶
ImageIO.write(createImageCode(70, 20, code), "jpeg", response.getOutputStream());
}
/**
* 生成一個隨機碼圖片
*
* @param width 隨機碼圖片寬度
* @param height 隨機碼圖片高度
* @param code 隨機碼
* @return 隨機碼圖片
*/
private BufferedImage createImageCode(int width, int height, String code) {
// 圖形實例
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 圖形屬性
Graphics g = image.getGraphics();
g.setColor(getRandomColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandomColor(160, 200));
// 隨機器
Random random = new Random();
// 干擾線
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);
}
// 繪制隨機碼
for (int i = 0; i < code.length(); i++) {
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(code.substring(i, i + 1), 13 * i + 6, 16);
}
g.dispose();
return image;
}
/**
* 生成一個隨機碼
*
* @param length 隨機碼位數
* @return 隨機碼
*/
private String createCode(int length) {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}
/**
* 獲取一種隨機的顏色
*/
private Color getRandomColor(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);
}
}
```
6. 自定義認證`MyUserDetailsService`
```java
@Service
public class MyUserDetailsService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
public MyUserDetailsService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("user",
passwordEncoder.encode("user"),
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
```
> 添加了一個用戶:`user`,密碼:`user`。
7. 自定義驗證碼異常類`ValidateCodeException`
```java
public class ValidateCodeException extends AuthenticationException {
private static final long serialVersionUID = 5022575393500654458L;
ValidateCodeException(String message) {
super(message);
}
}
```
> 繼承`AuthenticationException`,框架識別的異常類。
8. 驗證碼過濾器(暫時存儲在session中,在session中驗證)
```java
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("/login".equalsIgnoreCase(request.getRequestURI()) && "post".equalsIgnoreCase(request.getMethod())) {
try {
validateCode(request);
} catch (ValidateCodeException exception) {
response.getWriter().write(exception.getMessage());
return;
}
}
filterChain.doFilter(request, response);
}
/**
* 驗證Code
*/
private void validateCode(HttpServletRequest request) {
String imageCode = request.getParameter("imageCode");
String sessionCode = (String) request.getSession().getAttribute(ValidateController.SESSION_KEY);
if (StringUtils.isEmpty(imageCode)) {
throw new ValidateCodeException("code can't be null!");
}
if (StringUtils.isEmpty(sessionCode)) {
throw new ValidateCodeException("code isn't exist!");
}
if (!imageCode.equalsIgnoreCase(sessionCode)) {
throw new ValidateCodeException("code is error!");
}
request.getSession().removeAttribute("imageCode");
}
}
```
9. 完成配置類,組裝配置`BrowserSecurityConfig`
```java
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] WHITE_LIST = new String[]{"/login.html", "/code/image"};
@Autowired
private ValidateCodeFilter validateCodeFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.formLogin()
.loginPage("/login.html").loginProcessingUrl("/login");
http.authorizeRequests()
.antMatchers(WHITE_LIST).permitAll()
.anyRequest()
.authenticated();
http.csrf()
.disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
10. 訪問http://www.zhangpn.com/進行驗證。