spring security 6.3.3 登录用户简单校验小例子
项目结构
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>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.security</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</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>
<repositories>
<repository>
<id>maven_central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
</project>
application.yaml
server:
port: 8080
#logging:
# level:
# root: debug
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security-demo?serverTimezone=UTC
username: root
password: 123456
main
package com.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
自定义UserDetailsService实现类
package com.security.demo.config.authentication;
import com.security.demo.dao.DemoDao;
import com.security.demo.pojo.LoginUser;
import com.security.demo.pojo.DbUser;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 实现UserDetailsService接口的自定义UserDetailsService类型的类
* 在自定义的AuthenticationManager中会使用到该类
* 重写了loadUserByUsername(),里边获取到从DB里查出来相同username的信息
* 返回UserDetails类型的LoginUser,后续AuthenticationManager会自动进行是否有权限登录的校验
*/
@Component
public class LoginUserAuthorization implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginUserAuthorization.class);
@Resource
private DemoDao demoDao;
@Override
public UserDetails loadUserByUsername(String username) {
DbUser dbUser = null;
try {
dbUser = demoDao.queryByName(username);
} catch (Exception e) {
LOGGER.error("在这里准备记录一个可能不会发生的错误");
}
if (Objects.isNull(dbUser)) {
LOGGER.error("库里不存在该用户名字");
throw new UsernameNotFoundException("用户名或密码错误");
}
LoginUser loginUser = new LoginUser();
loginUser.setUser(dbUser);
return loginUser;
}
}
security配置类
package com.security.demo.config;
import com.security.demo.config.authentication.LoginUserAuthorization;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
/**
* security的配置类
*/
@Configuration
public class SecurityConfig {
@Resource
private LoginUserAuthorization loginUserAuthorization;
/**
* 定义了password使用的加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 为AuthenticationManager里定义了UserDetailsService当中方法的执行逻辑和password的加密方式
* ProviderManager是AuthenticationManager接口的具体实现类
* 该类里实现了authenticate方法是做具体验证的
* @return
*/
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(loginUserAuthorization);
provider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(provider);
}
/**
* security全是由过滤器链的方式实现的
* 该例中只做了一些最简单的配置
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//本地测试关了csrf
http.csrf(AbstractHttpConfigurer::disable);
//只允许/demo/register接口可以不受任何限制的访问
http.authorizeHttpRequests(r -> r.requestMatchers("/demo/register").permitAll()
//对于其它任何接口的访问都要经过身份认证方可
.anyRequest().authenticated());
//使用security美观简洁的默认登录页
http.formLogin(Customizer.withDefaults());
//返回配置好的过滤器链逻辑
return http.build();
}
}
pojo - DbUser
package com.security.demo.pojo;
import java.io.Serializable;
/**
* 和数据库里所有字段分别对应的实体类
*/
public class DbUser implements Serializable {
private int id;
private String username;
private String password;
private String source;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
@Override
public String toString() {
return "DbUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", source='" + source + '\'' +
'}';
}
}
pojo - LoginUser
package com.security.demo.pojo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* 实现了UserDetails接口的实体类,以供security用来做后续是否有权限登录的校验
*/
public class LoginUser implements Serializable, UserDetails {
/**
* 使用DbUser做为该实体类的字段
* 因为LoginUser的getUsername()和getPassword()两个方法返回的值
* 可以从DbUser里获得
*/
private DbUser dbUser;
public DbUser getUser() {
return dbUser;
}
public void setUser(DbUser dbUser) {
this.dbUser = dbUser;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return dbUser.getPassword();
}
@Override
public String getUsername() {
return dbUser.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return UserDetails.super.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return UserDetails.super.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return UserDetails.super.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return UserDetails.super.isEnabled();
}
@Override
public String toString() {
return "LoginUser{" + "DbUser=" + dbUser + "}";
}
}
controller
package com.security.demo.controller;
import com.alibaba.fastjson2.JSON;
import com.security.demo.pojo.DbUser;
import com.security.demo.service.DemoService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/demo")
public class DemoController {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoController.class);
@Resource
private DemoService demoService;
/**
* 获取所有已存在的用户
* @return
*/
@RequestMapping("/list")
public String list() {
List<DbUser> dbUsers = demoService.queryAll();
return JSON.toJSONString(dbUsers);
}
/**
* 注册一个新用户
* @param dbUser
* @return
*/
@RequestMapping("/register")
public String register(@RequestBody DbUser dbUser) {
String username = dbUser.getUsername();
int row = demoService.register(dbUser);
if (row == 0) {
username = username.concat(": 注册失败,用户名已存在");
LOGGER.error("{}:", username);
}
if (row == 1) {
username = username.concat(": 注册成功");
LOGGER.info("{}:", username);
}
return username;
}
}
service
package com.security.demo.service;
import com.security.demo.pojo.DbUser;
import java.util.List;
public interface DemoService {
List<DbUser> queryAll();
int register(DbUser dbUser);
}
service impl
package com.security.demo.service.impl;
import com.security.demo.pojo.DbUser;
import com.security.demo.dao.DemoDao;
import com.security.demo.service.DemoService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class DemoServiceImpl implements DemoService {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoServiceImpl.class);
@Resource
private DemoDao demoDao;
@Resource
private PasswordEncoder passwordEncoder;
/**
* 获取所有用户信息
* @return
*/
@Override
public List<DbUser> queryAll() {
return demoDao.queryAll();
}
/**
* 注册一个新用户
* @param dbUser
* @return
*/
@Override
public int register(DbUser dbUser) {
int row = 0;
if (checkUserNameIsExist(dbUser.getUsername())) {
return row;
}
try {
String password = dbUser.getPassword();
String encode = passwordEncoder.encode(password);
dbUser.setPassword(encode);
row = demoDao.register(dbUser);
} catch (Exception e) {
LOGGER.error("用户在注册时出现异常");
}
return row;
}
/**
* 在注册的时候检查该用户名字是否已存在
* @param username
* @return
*/
private boolean checkUserNameIsExist(String username) {
boolean result = false;
try {
DbUser dbUser = demoDao.checkUserByName(username);
if (!Objects.isNull(dbUser)) {
result = true;
LOGGER.error("新用户名字已存在");
}
} catch (Exception e) {
LOGGER.error("在这里准备记录一个可能不会发生的错误");
}
return result;
}
}
dao
package com.security.demo.dao;
import com.security.demo.pojo.DbUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface DemoDao {
//获取所有用户信息
List<DbUser> queryAll();
//根据username获取对应的用户信息
DbUser queryByName(String username);
//注册一个新用户
int register(DbUser dbUser);
//检查username是否已存在
DbUser checkUserByName(String username);
}
resources - dao
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.security.demo.dao.DemoDao">
<select id="queryAll" resultType="com.security.demo.pojo.DbUser">
select id, username, source from user
</select>
<select id="queryByName" parameterType="string" resultType="com.security.demo.pojo.DbUser">
select * from user where username = #{username}
</select>
<insert id="register" parameterType="com.security.demo.pojo.DbUser">
insert into user (username, password, source) values (#{username}, #{password}, #{source})
</insert>
<select id="checkUserByName" parameterType="string" resultType="com.security.demo.pojo.DbUser">
select username from user where username = #{username}
</select>
</mapper>