文章目录
- 1. 基于内存的用户定义认证
- 1. 配置文件方式
- 2. 配置类中重写configure方式
- 3. 自定义 InMemoryUserDetailsManager 方式
- 2. 提供用户数据源的接口 UserDetailsService
- 1. UserDetails 接口
- 2. UserDetailsService 接口
- 3. 基于MyBatis数据源的用户定义认证
1. 基于内存的用户定义认证
测试接口:启动项目访问 localhost:8080/info
@RestController
public class UserController {
@RequestMapping("/info")
public void info(HttpServletRequest request){
// 当前登录用户的用户名
String remoteUser = request.getRemoteUser();
Principal userPrincipal = request.getUserPrincipal();
Authentication authentication = (Authentication) userPrincipal;
// 判断当前用户是否具备某一个指定的角色
boolean admin = request.isUserInRole("admin");
// 登录用户的用户名
System.out.println("remoteUser = " + remoteUser);
System.out.println("authentication.getName():"+authentication.getName());
System.out.println("admin = " + admin);
}
}
1. 配置文件方式
默认的用户定义在SecurityProperties里边,是一个静态内部类,如果要定义自己的用户名密码,必然是要去覆盖默认配置,在配置文件中配置:
spring.security.user.name=admin
spring.security.user.password=admin
spring.security.user.roles=ADMIN
2. 配置类中重写configure方式
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123").roles()
.and()
.withUser("lisi").password("123").roles();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
.csrf().disable();
}
@Bean
PasswordEncoder passwordEncoder() {
//暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例
return NoOpPasswordEncoder.getInstance();
}
}
启动项目,此时就可以使用这里配置的用户登录了。
3. 自定义 InMemoryUserDetailsManager 方式
SpringSecurity 支持多种用户定义方式,自定义用户其实就是使用UserDetailService的不同实现类来提供用户数据,同时将配置好的UserDetailsService配置给AuthenticationManagerBuilder,系统再将UserDetailsService提供给AuthenticationProvider使用。
通过自定义 InMemoryUserDetailsManager 来看一下基于内存的用户是如何自定义的。
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 创建对象设置用户名,密码,角色 (密码加上{noop}代表密码不加密,明文存储)
manager.createUser(User.withUsername("zhangsan").password("123").roles("admin").build());
manager.createUser(User.withUsername("lisi").password("123").roles("user").build());
auth.userDetailsService(manager);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
.csrf().disable();
}
@Bean
PasswordEncoder passwordEncoder() {
//暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例
return NoOpPasswordEncoder.getInstance();
}
}
2. 提供用户数据源的接口 UserDetailsService
1. UserDetails 接口
SpringSecurity 中定义了UserDetails 接口来规范开发者自定义的用户对象,接口定义如下:
public interface UserDetails extends Serializable {
// 返回当前账户锁具备的权限
Collection<? extends GrantedAuthority> getAuthorities();
// 返回当前账户的密码
String getPassword();
// 返回当前账户的用户名
String getUsername();
// 返回当前账户是否未过期
boolean isAccountNonExpired();
// 返回当前账户是否未锁定
boolean isAccountNonLocked();
// 返回当前账号凭证是否未过期
boolean isCredentialsNonExpired();
// 返回当前账号是否可用
boolean isEnabled();
}
2. UserDetailsService 接口
这是用户对象的定义,而负责提供用户数据源的接口是UserDetailsService,UserDetailService中只有一个查询用户的方法:
public interface UserDetailsService {
// username是用户在认证时传入的用户名,最常见的是用户在登录表单中输入的用户名
// 拿到用户名之后,再去数据库中查询用户,最终返回一个UserDetails实例
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
在实际项目中,一般需要自定义UserDetailsService的实现。如果没有自定义UserDetailsService的实现,Spring Security 也为UserDetailsService 提供了默认实现:
(1) UserDetailsManager 在UserDetailsService 的基础上,定义了5种方法:
public interface UserDetailsManager extends UserDetailsService {
// 创建用户
void createUser(UserDetails var1);
// 更新用户
void updateUser(UserDetails var1);
// 删除用户
void deleteUser(String var1);
// 修改密码
void changePassword(String var1, String var2);
// 判断用户是否存在
boolean userExists(String var1);
}
(2) JdbcDaoImpl 在UserDetailsService 的基础上,通过spring-jdbc实现了从数据库中查询用户的方法;
(3) InMemoryUserDetailsManager 实现了UserDetailsService 中关于用户的增删改查方法,不过都是基于内存的操作,数据没有持久化;
(4) JdbcUserDetailsManager 继承自 JdbcDaoImpl 同时又实现了 UserDetailsManager 接口,因此可以通过JdbcUserDetailsManager 实现对用户的在呢过删改查操作,这些操作都会持久化到数据化中,但是操作数据库中用户的SQL都是提前写好的,不够灵活,因此在是开发中 JdbcUserDetailsManager 用的并不多;
(5) CachingUserDetailsService 的特点是会将 UserDetailsService 缓存起来;
(6) UserDetailsServiceDelegator 提供了 UserDetailsService 的懒加载功能;
当我们使用Spring Security 时,如果仅仅引入了一个Spring Security依赖,则默认使用的用户就是 InMemoryUserDetailsManager 提供的;
SpringBoot 之所以能做到零配置使用SpringSecurity,就是因为它提供了众多的自动化配置类。其中UserDetailsService 的自动化配置类时UserDetailsServiceAutoConfiguration;
3. 基于MyBatis数据源的用户定义认证
使用MyBatis做数据持久化是目前大多数企业应用采取的方案,SpringSecurity中结合MyBatis可以灵活的定制用户表和角色表。
首先设计三张表,分别为用户表,角色表,以及用户角色关联表,三张表的关系如图所示:
create table role(
id int(11) not null auto_increment,
name varchar(32) default null,
nameZh varchar(32) default null,
primary key(id)
)engine=innodb default charset=utf8
create table user(
id int(11) not null auto_increment,
username varchar(32) default null,
password varchar(255) default null,
enable tinyint(1) default null,
accountNonExpired tinyint(1) default null,
accountNonLocked tinyint(1) default null,
credentialsNonExpired tinyint(1) default null,
primary key(id)
)engine=innodb default charset=utf8
create table user_role(
id int(11) not null auto_increment,
uid int(11) default null,
rid int(11) default null,
primary key(id),
key uid(uid),
key rid(rid)
)engine=innodb default charset=utf8
向数据库中添加几条模拟数据:
INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (1, 'ROLE_dba', '数据库管理员');
INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (3, 'ROLE_user', '用户');
INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (1, 'root', '123', 1, 1, 1, 1);
INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (2, 'admin', '123', 1, 1, 1, 1);
INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (3, 'zhangsan', '123', 1, 1, 1, 1);
INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (1, 1, 1);
INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (2, 1, 2);
INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (3, 2, 2);
INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (4, 3, 3);
在SpringSecurity项目中引入MyBatis和MySQL的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在resources/application.yml中配置数据库基本连接信息:
spring:
application:
name: uua
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
mybatis:
mapper-locations: classpath:mapper/*.xml
自定义用户类需要实现UserDetails接口,并实现接口中的方法:
@Data
public class Role {
private Integer id;
private String name;
private String nameZh;
}
@Data
public class UserRole {
private Integer id;
private Integer uid;
private Integer rid;
}
@Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean accountNonExpired;
private Boolean accountNonLocked;
private Boolean credentialsNonExpired;
// 保存用户所具备的角色信息
private List<Role> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(Role role:roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
实现UserDetailsService接口自定义MyUserDetailsService以及对应的数据库查询方法:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if(Objects.isNull(user)){
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(userMapper.getRolesByUid(user.getId()));
return user;
}
}
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getRolesByUid(Integer uid);
}
<?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.imooc.uua.dao.UserMapper">
<select id="loadUserByUsername" resultType="com.imooc.uua.entity.User">
select * from user wherr username=#{username};
</select>
<select id="getRolesByUid" resultType="com.imooc.uua.entity.Role">
select r.*
from user_role ur
join role r
on ur.rid=r.id and ur.uid=#{uid}
</select>
</mapper>
SecurityConfig中注入UserDetailsService :
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
.csrf().disable();
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
启动项目利用数据库中添加的模拟用户进行登录测试,就可以登录成功了。