Spring Security
一、Spring security简介
spring security 的核心功能主要包括:
- 认证(你是谁)
- 授权(你能做什么)
- 攻击防护(防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDSKdhh0-1642412252279)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20220114173759485.png)]
比如,对于username,password认证过滤器来说
- 检查是否是一个登录请求
- 是否包含username和password(也就是该过滤器需要的一些认证信息)
- 如果不满足则放行给下一个
下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。
最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。
Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
注意:绿色的过滤器可以配置是否生效,其他的都不能控制。
Spring非常流行和成功的java引用开发框架,SpringSecurity正是Spring家族中的成员。Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个主要区域是"认证"和"授权"(或者访问控制),一般来说Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两部分,这两点也是SpringSecurity重要核心功能。
- 用户认证(Authentication):验某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程,通俗点说就是系统任务用户是否能都登录
- 用户授权(Authorization):指的是验证某个用户是否有权限执行某个操作,在一个系统中,不同用户所具有的权限是不同的,比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改,一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是
使用实例
1、通过配置类设定登录用户
1.1、pom.xml文件
<!--Spring Security依赖添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
1.2、配置文件WebSecurityConfig
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//创建密码编码器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//
auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
//设置用户名密码(若passwordEncoder中设置了密码编辑器,则设置密码时要设置密码编辑器)
//.roles("") 方法设置角色
.withUser("admin")
.password(bCryptPasswordEncoder.encode("123123")).roles("ADMIN","USER")
.and()
.withUser("user")
.password(bCryptPasswordEncoder.encode("123123")).roles("USER")
.and()
.withUser("tmp")
.password(bCryptPasswordEncoder.encode("123123")).roles();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//获取Http的设置authorizeRequests授权请求方法
//antMatchers添加无需授权请求permitAll()设置为全部用户允许
//.anyRequest().authenticated() 其他请求都需要认证
http.authorizeRequests()
//添加/product/** 下的所有请求只能由user角色才能访问
.antMatchers("/product/**").hasRole("USER")
//添加/admin/** 下的所有请求只能由admin角色才能访问
.antMatchers("/admin/**").hasRole("ADMIN")
//没有定义的请求,所有的角色都可以访问(tmp也可以)
.anyRequest().authenticated()
.and()
//当不知设登录页面时,使用的时默认的登录页面
.formLogin()
/*
//设置登录页面为login 默认情况下为自带的默认登录页面
.loginPage("/login")
//所有人均可允许
.permitAll()
.and().logout()
//logoutUrl("/logout"); 设置注销路径
//默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,
// 清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,
// 然后重定向到”/login?success”
//登录后所有人均可访问
.permitAll();
*/
.and()
.httpBasic();
}
}
2、结合数据库中的用户信息
2.1、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>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.erha.springsecurity</groupId>
<artifactId>springsecuritydemo03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecuritydemo03</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Spring Security依赖添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2、配置文件application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/jqdatatset?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# spring data jpa 相关配置
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
ddl-auto: update
2.3、主启动类
@SpringBootApplication
@ComponentScan(basePackages = "com.erha.springsecurity")
@EnableJpaRepositories(basePackages = "com.erha.springsecurity.dao")
@EntityScan(basePackages = "com.erha.springsecurity.entity")
public class Springsecuritydemo03Application {
public static void main(String[] args) {
SpringApplication.run(Springsecuritydemo03Application.class, args);
}
}
2.4、entity对象类
@Entity
@Table(name = "user")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
@Id
@GenericGenerator(name = "idGenerator",strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "login",unique = true,nullable = false,length = 64)
private String login;
@Column(name = "password",nullable = false,length = 64)
private String password;
@Column(name = "role",nullable = false,length = 64)
private String role;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", login='" + login + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != null ? !id.equals(user.id) : user.id != null) return false;
if (login != null ? !login.equals(user.login) : user.login != null) return false;
if (password != null ? !password.equals(user.password) : user.password != null) return false;
return role != null ? role.equals(user.role) : user.role == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (login != null ? login.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (role != null ? role.hashCode() : 0);
return result;
}
}
2.5、dao层User对应的Repostory
@Repository
public interface UserRepository extends JpaRepository<User,String> {
User findUserByLogin(String login);
}
2.6、Service层UserDetailsService实现类
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserRepository userRepository;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userRepository.findUserByLogin(login);
if(user == null){
throw new UsernameNotFoundException("User "+login+" was not found in db");
}
//设置角色
Collection<GrantedAuthority> objects = new ArrayList<>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
objects.add(grantedAuthority);
//第二个参数为密码,若存在密码编码器,需要再次使用密码编码器
/*
* 参数说明
* 1、username 用户名参数
* 2、password 用户密码 设置密码编辑器后,可在此设置密码编辑器
* 3、Collection<? extends GrantedAuthority> authorities 用户所有角色
**/
return new org.springframework.security.core.userdetails.User(login, bCryptPasswordEncoder.encode(user.getPassword()),objects);
}
}
2.7、WebSecurity配置类
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Override
//关于用户的设置
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) //设置自定义userDetailsService
.passwordEncoder(passwordEncoder());
}
@Override
//页面校验设置
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/product/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()//任何角色都可以完成
.and()
.formLogin()
.and()
.httpBasic()
.and().logout().logoutUrl("/logout");
}
@Bean // 定义密码编码器
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
2.8、Controller层
AdminController类
@RestController
@RequestMapping(value = "/admin")
public class AdminController {
@RequestMapping(value = "/hello.do")
public String hello(){
return "admin hello";
}
}
ProductController
@RestController
@RequestMapping(value = "/product")
public class ProductController {
@RequestMapping(value = "/hello.do")
public String hello(){
return "product hello";
}
}
补充
1、修改当前登录用户密码
若涉及到要修改当前登录用户密码的情况,可用如下方法进行编写
重点是:若使用了密码编码器 在密码更新时,要将更新密码使用密码编码器进行编码
@Mapper
@Repository
public interface UserMapper {
...
@Update("update user set password = #{newPwd} where username = #{username}")
int updatePwd(String username, String newPwd);
}
UserInfoService.java类中添加更新密码的操作方法:
@Service
public class UserInfoService {
...
public int updatePwd(String oldPwd, String newPwd) {
// 获取当前登录用户信息(注意:没有密码的)
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = principal.getUsername();
// 通过用户名获取到用户信息(获取密码)
UserInfo userInfo = userInfoMapper.getUserInfoByUsername(username);
// 判断输入的旧密码是正确
if (passwordEncoder.matches(oldPwd, userInfo.getPassword())) {
// 不要忘记加密新密码
return userInfoMapper.updatePwd(username, passwordEncoder.encode(newPwd));
}
return 0;
}
}
2、注解完成控制层方法的权限访问
@PreAuthorize(“hasAnyRole(‘user’)”)
@RestController
public class HelloController {
@Autowired
private UserInfoService userInfoService;
@GetMapping("/get-user")
public UserInfo getUser(@RequestParam String username){
return userInfoService.getUserInfo(username);
}
@PreAuthorize("hasAnyRole('USER')") // 只能user角色才能访问该方法
@GetMapping("/user")
public String user(){
return "user角色访问";
}
@PreAuthorize("hasAnyRole('ADMIN')") // 只能admin角色才能访问该方法
@GetMapping("/admin")
public String admin(){
return "admin角色访问";
}
}