一、SpringSecurity
Spring Security 是 Spring 家族中的成员,基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
两个主要的策略是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
- 用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问
该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录 - 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户
所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
SpringSecurity 对比 Shiro
SpringSecurity:
- 和 Spring 无缝整合。
- 全面的权限控制。
- 专门为 Web 开发而设计。
- 旧版本不能脱离 Web 环境使用。
- 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
引入核心模块就可以脱离 Web 环境。 - 重量级
Shiro:
- Apache 旗下的轻量级权限控制框架。
- 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
的互联网应用有更好表现。 - 通用性。
- 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
- 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。
二、SpringSecurity 用户认证
创建一个SpringBoot项目,在pom中引入SpringSecurity
的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
这里先写一个测试接口:
@RestController
public class TestController {
@GetMapping("/admin/test")
public String admin(){
return "/admin/test success";
}
}
接口中什么也没做,就返回了一个字符串,下面启动项目,在浏览器访问测试接口:http:localhost:8080/admin/test
可以发现竟然跳转到了一个登录页面,但是我们什么都没有做呀,是SpringSecurity 已经被SpringBoot自动集成好了,现在已经起作用了,那我们要怎么登录呢?,其实看下打印的日志,已经把密码告诉我们了:
用户名默认是user
,再输入上面的密码,就会发现接口可以访问了:
现在就会发现用户名密码,并不是我们设置的,显然不符合我们的要求,这里我们可以在配制文件中把用户名密码给指定好,修改application.yml
文件:
server:
port: 8080
spring:
security:
user:
name: bxc
password: 1234
重启项目,发现使用上面配制的密码就可以登录了。
用户名和密码即使可以在配制文件中配制,显然也是不符合我们的要求的,如果我们需要多个用户呢,下面就可以集成security
提供的WebSecurityConfigurerAdapter
类,可以做更细粒话的用户认证操作,下面创建一个WebSecurityConfig
类继承WebSecurityConfigurerAdapter
:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password(new BCryptPasswordEncoder().encode("1234")).authorities("admin");
auth.inMemoryAuthentication().withUser("common").password(new BCryptPasswordEncoder().encode("1234")).authorities("common");
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").fullyAuthenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
上面的配制,添加了两个用户,其中authorities
表示该用户所拥有的权限或角色,现在还没有授权操作,这里可以随便给一个就可以,后面我们讲解授权的使用。
其中configure(HttpSecurity http)
这个方法可以细粒度的配制验证模式和那些地址对应那些角色或权限,上面配制了所有的接口都需要进行认证才可以访问,并且认证方式是form表单的形式,并且关闭了csrf
。
下面再次访问上面的接口,使用上面配制的两个用户就可以实现登录了。
一般用户名和密码都是存储在数据库中的,要实现动态的用户认证,下面我们就以上面这个配制为基础继续学习,我们可以看下AuthenticationManagerBuilder
所支持的认证方式:
可以看到有个userDetailsService
方式可以传入一个UserDetailsService
对象,而UserDetailsService
中又有个loadUserByUsername
方法,传入的参数就是用户名。
所以这里可以创建一个自己的UserService
类,实现UserDetailsService
,并在loadUserByUsername
中查询数据库,并将用户信息放入UserDetails
对象中,返回出去就可以实现用户的动态认证了。
三、用户动态认证
既然是用户动态的认证,肯定需要从数据库中取数据,所以先创建一个数据库,并新建表user
:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`enabled` tinyint(1) NOT NULL,
`locked` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
下面就要引入关于数据库的依赖,这里使用mybatisplus
,在pom 中添加依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
配制文件中添加数据库的连接信息:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.40.1:3307/testdb?useUnicode=true&characterEncoding=utf8
username: root
password: root123
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
mapper-locations: classpath:mapper/xml/*.xml
type-aliases-package: com.bxc.securitydemo.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
创建UserEntity
实现UserDetails
,在loadUserByUsername
方法中直接返回UserEntity
就可以了:
@Data
@TableName(value = "user")
public class UserEntity implements UserDetails {
private int id;
private String username;
private String password;
@Getter(value = AccessLevel.NONE)
private Boolean enabled;
private Boolean locked;
@TableField(exist = false)
private List<GrantedAuthority> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
创建UserMapper
直接继承BaseMapper
获取操作方法
@Mapper
@Repository
public interface UserMapper extends BaseMapper<UserEntity> {
}
创建UserService
实现UserDetailsService
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
.eq(UserEntity::getUsername, username);
UserEntity userEntity = userMapper.selectOne(wrapper);
if (userEntity == null) {
throw new UsernameNotFoundException("用户不存在!");
}
List<GrantedAuthority> auths = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_admin");
auths.add(authority);
userEntity.setRoles(auths);
return userEntity;
}
public boolean register(String userName, String password) {
UserEntity entity = new UserEntity();
entity.setUsername(userName);
entity.setPassword(new BCryptPasswordEncoder().encode(password));
entity.setEnabled(true);
entity.setLocked(false);
return userMapper.insert(entity) > 0;
}
}
在该类中写了两个方法,一个是UserDetailsService
类需要的loadUserByUsername
在登录的时候会触发该方法,另一个就是注册了,就是做了向数据库添加数据的功能。
下面修改我们的WebSecurityConfig
配制文件:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(password());
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").fullyAuthenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/register/**");
}
}
最后写一个注册的入口controller
:
@RestController
public class UserController {
@Autowired
UserService userService;
@PostMapping("/register")
public String register(String userName, String passwd) {
return userService.register(userName, passwd) ? "success" : "err";
}
}
下面开始测试:
先注册一个用户bxctest
,密码为123456
:
下面使用bxctest
登录:
现在就实现了数据库动态认证了。
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!