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/进行验证。