赞
踩
在Spring Security实现用户认证一中说到,请求被过滤器UsernamePasswordAuthenticationFilter
处理生成UsernamePasswordAuthenticationToken
,实际上这里的token只是临时的,并没有进行认证,需要一个AuthenticationProvider
提供认证方式。
如下官方所提供的一张原图清楚说明了后续的认证过程。
从上图可以看出,UsernamePasswordAuthenticationToken
,ProviderManager
类对在Token
匹配AuthenticationProvider
。
对于采用用户名和密码的认证方式,匹配到的是DaoAuthenticationProvider
。进入到DaoAuthenticationProvider
,这个类需要UserDetailsService
和PasswordEncoder
。
UserDetailsService
里面存储着用户的细节UserDetails
,包括用户名、密文密码、权限等信息。PasswordEncoder
是用来对密码进行加密的,默认的加密算法是BCryptPasswordEncoder
。
在DaoAuthenticationProvider
拿到用户请求中的username
和明文password
,也就是包裹在UsernamePasswordAuthenticationToken
中的字段principal
和credentials
。如下图所示:
UserDetailsService
里面是密文密码,所以需要应用对应的加密算法将用户的明文密码映射成密文密码。
UserDetailsService
首先通过username
查找UserDetails
,将找到的UserDetails
取出密文密码,再对比两个密文密码的一致性去认证用户信息。
下图展示了认证成功后的UsernamePasswordAuthenticationToken
,认证成功的Token将会拿到用户的角色信息和授权信息。最终,返回的 UsernamePasswordAuthenticationToken 被设置在 SecurityContextHolder 上。
讲到这里我们大致知道怎么修改了。我们需要一个读取UserDetails
的一个UserDetailsService
,以及一个密码编码方法PasswordEncoder
。
其他配置请参考往期内容。
在不进行任何配置条件下,系统会默认生成一个username为user,密码随机的用户(在控制台打印)。
如果想要修改该默认用户的用户名和密码。可以在application.yml
文件中添加如下内容:
spring:
security:
user:
name: user
password: 1234
这是就可以用user和1234进行登录。
下面请看如何添加新的用户到内存中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
下面配置了一个基于内存的UserDetailsService,并且向其中添加了一个用户(root,root),该用户角色定义为USER。重新启动服务器可以使用该用户登录。这时系统默认的用户将会失效。
这里拥有我们所需要的两个条件:UserDetailsService
和 PasswordEncoder
。withDefaultPasswordEncoder
会采用系统默认的密码编码器。
package com.song.cloud.config; 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.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity //开启SpringSecurity自动配置(springboot中可以省略) public class WebSecurityConfig { //为存储在内存中的基于用户名/密码的认证提供支持。 @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withDefaultPasswordEncoder().username("root").password("root").roles("USER").build()); return manager; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // ... } }
上面的配置变可以启动服务器测试,输入(root,root)可以进行登录。
MySql 8.0.35,druid, mybatis
<!-- 数据库依赖 --> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
分别用于存放用户和权限。
create table users
(
username varchar(50) not null primary key,
password varchar(500) not null,
enabled boolean not null
);
create table authorities
(
username varchar(50) not null,
authority varchar(50) not null,
constraint fk_authorities_users foreign key (username) references users (username)
);
create unique index ix_auth_username on authorities (username, authority);
application.yml配置
spring: application: name: spring-security security: user: name: user password: 1234 datasource: driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource username: root # 修改成自己的数据库username和password password: root # 请把test_db修改成自己数据库名字 url: jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8&useSSL=false&serverTimeZone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true server: port: 4555 logging: level: web: debug mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.song.cloud.entities configuration: map-underscore-to-camel-case: true
下面请根据实际情况修改,会用mybatis-generator插件的请自行生成。不会请查阅其他教程。
package com.song.cloud.entities; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; /** * 表名:t_users_test */ @Table(name = "t_users_test") @Setter @Getter @NoArgsConstructor @AllArgsConstructor @ToString public class User { /** * id */ @Id @GeneratedValue(generator = "JDBC") private Long id; /** * 用户名 */ private String username; /** * 密码hash */ @Column(name = "password_hash") private String passwordHash; /** * 是否启用 */ private Boolean enable; }
实现的JdbcUserDetailsManager
类已经封装了默认的增删改查的sql语句,但是事实上这样做非常麻烦,也不符合编程习惯,虽然官方提供了对语句的修改功能。
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import javax.sql.DataSource; @Configuration @EnableWebSecurity //开启SpringSecurity自动配置(springboot中可以省略) public class WebSecurityConfig { @Bean public JdbcUserDetailsManager jdbcUserDetailsManager(DataSource dataSource){ JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource); // ----------------------- 向数据库添加用户和权限 ---------------- UserDetails user = User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build(); UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("admin").roles("ADMIN", "USER").build(); manager.createUser(user); manager.createUser(admin); // -----------------------结束向数据库添加用户和权限 ---------------- return manager; } // ... }
现在,可以使用这两个用户进行登录。
在服务器启动时,这两个用户便被写进数据库中。下次启动需要注释向数据库添加用户和权限
的这些,或者将数据库中的users和authorities两个表数据删掉,否则会重复键值而报错, 先删authorities的数据。
@PostMapping("/user/add") public UserDetails addUser(@RequestBody User user) { System.out.println(user); PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); String pd_encode = delegatingPasswordEncoder.encode(user.getPasswordHash()); UserDetails userDetails = org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(pd_encode) .roles("USER") //自己定义 .disabled(!user.getEnable()) .build(); jdbcUserDetailsManager.createUser(userDetails); return userDetails; }
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
测试地址:http://localhost:port/swagger-ui/index.html
改成项目端口号
进入测试地址前,需要使用之前添加进入db的账户登录。
测试数据:
{
"username": "test",
"passwordHash": "test",
"enable": true
}
测试登录:
怎么更加随心所欲的定制一下。
建议使用@Service
注解形式注册,由于需要使用dao层的服务,采用自动注入的形式必须使用@Service
标识为bean,交给spring IoC管理,才能实现自动注入dao层服务。
import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.provisioning.UserDetailsManager; import tk.mybatis.mapper.entity.Example; @Service //建议使用@Service 注解形式注册 public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService { @Resource private UserMapper userMapper; // 自定义的dao层 @Override public UserDetails updatePassword(UserDetails user, String newPassword) { return null; } @Override public void createUser(UserDetails userDetails) { User user = new User(); user.setUsername(userDetails.getUsername()); user.setPasswordHash(userDetails.getPassword()); userMapper.insertSelective(user); } @Override public void updateUser(UserDetails userDetails) { // 自己定义 } @Override public void deleteUser(String username) { // 自己定义 } @Override public void changePassword(String oldPassword, String newPassword) { // 自己定义 } @Override public boolean userExists(String username) { // 自己定义 return false; } /** * 从数据库中获取用户信息,继续引入持久层,UserMapper * * @param username the username identifying the user whose data is required. * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Example selectByUserId = new Example(User.class); Example.Criteria criteria = selectByUserId.createCriteria(); criteria.andEqualTo("username", username); User user = userMapper.selectOneByExample(selectByUserId); if (user == null) { throw new UsernameNotFoundException(username); } Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(() -> "USER_LIST"); authorities.add(() -> "USER_ADD"); return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPasswordHash()) .disabled(false) .credentialsExpired(false) .accountLocked(false) .roles("ADMIN") .build(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。