Spring Security
Introduction
Spring Security 是基于 Spring 的安全框架,Spring Security 提供全面的安全性解决方案,同时在 Web Request 和 Method 处理身份认证和授权,在 Spring Framework 基础上,Spring Security 充分利用了 Soring 的 DI 和 AOP 特性,为应用系统提供了声明式的安全访问控制功能,是一个轻量级的框架,可以很好的与 Spring 及 Spring MVC 集成
核心功能
认证(Who are you?)
授权(What you can do?)
原理
基于 Servlet Filter AOP 实现认证和授权
Spring Security 最佳实践
使用系统自定义用户及 yml 中自定义的用户进行登录
创建 Maven 项目
加入依赖,SpringBoot web stater 和 security-stater
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
复制代码
3.创建启动 SpringBoot 启动类
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
复制代码
4.创建 controller 包及 Contrller 控制器
@RestController
public class HelloSecurityController {
@GetMapping("/security")
public String helloSecurity(){
return "Hello Spring Security!";
}
}
复制代码
5.启动 SecurityApplication,控制台会生成密码,请看下图所示
浏览器地址栏输入 http://localhost:8080/
输入用户名 user 及控制台生成的密码,即可登录系统访问 HelloSecurityController
如果密码输入错误,则会有相应的提示
6.以上用户名密码都是由系统自动生成的,如果需要自定义用户名密码则需要在配置文件中进行配置,重新启动,输入设置的用户名密码即可登录
spring:
security:
user:
name: admin
password: admin
复制代码
7.关闭登录验证对启动类进行修改,{}中可以放入多个配置类
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
复制代码
使用设置在内存中的用户进行登录
继承 WebSecurityConfigurerAdapter,重写 configure 方法来控制安全管理的内容,将重写的类交由 Spring IOC 进行管理,可以自定义认证功能,重写是需要使用两个注解 @Configuration 和 @EnableWebSecurity
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder pe = passwordEncoder();
// 设置内存中用户名和密码
auth.inMemoryAuthentication().withUser("IronMan").password(pe.encode("12345")).roles();
auth.inMemoryAuthentication().withUser("SpiderMan").password(pe.encode("12345")).roles();
auth.inMemoryAuthentication().withUser("Thor").password(pe.encode("thor")).roles();
}
// 密码加密类,该类由SpringIOC进行管理,id默认为passwordEncoder
@Bean
public PasswordEncoder passwordEncoder(){
// 实现密码加密
return new BCryptPasswordEncoder();
}
}
复制代码
启动前关闭启动类上的 exclude 中的内容,启动成功后使用设置的用户名密码进行登录系统,如果改配置类中设置的密码没有加密会报错“java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"”
基于 ROLE 的身份认证
修改 config 包中的 MyWebSecurityConfig 类,给用户设置角色,代码如下:
/**
* prePostEnabled = true表示可以使用@PreAuthorize注解和@PostAuthorize方法级别的注解
*/
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级别的安全控制
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder pe = passwordEncoder();
// 设置内存中用户名和密码,并设置角色,一个用户可以有多个角色
auth.inMemoryAuthentication().withUser("IronMan").password(pe.encode("12345")).roles("admin");
auth.inMemoryAuthentication().withUser("SpiderMan").password(pe.encode("12345")).roles("user");
auth.inMemoryAuthentication().withUser("Thor").password(pe.encode("thor")).roles("user","admin");
}
// 密码加密类,该类由SpringIOC进行管理,id默认为passwordEncoder
@Bean
public PasswordEncoder passwordEncoder(){
// 实现密码加密
return new BCryptPasswordEncoder();
}
}
复制代码
修改 HelloSecurityController,定义角色访问路径
@RestController
public class HelloSecurityController {
@GetMapping("/security")
public String sayHello(){
return "使用内存中的用户信息进行认证";
}
//指定user和admin都可以访问的方法
@GetMapping("/hello")
@PreAuthorize(value = "hasAnyRole('admin','user')")
public String helloUserAndAdmin(){
return "user和admin角色都可以访问";
}
@GetMapping("/admin")
@PreAuthorize(value = "hasAnyRole('admin')")
public String helloAdmin(){
return "只有admin可以访问";
}
}
复制代码
启动应用,使用 IronMan/12345 访问/hello
访问/admin 路径
重启 tomcat,使用 Thor/thor 访问/hello
访问/admin
实现了不同的角色拥有不同路径的访问权限
基于 JDBC 的用户认证
首先修改 pom.xml,增加 MySQL 依赖及 JPA
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
复制代码
创建实体类 UserInfo,添加 get/set 方法
@Entity
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String role;
}
复制代码
创建持久层 UserInfoDao,继承 JpaRepository,定义方法 findByUsername(String username)
public interface UserInfoDao extends JpaRepository<UserInfo,Long>{
//按照username查询数据库
UserInfo findByUsername(String username);
}
复制代码
创建 service 层 UserInfoService 接口,并创建 UserInfoServiceImpl 实现类实现该接口
public interface UserInfoService {
UserInfo findUserInfo(String username);
}
复制代码
不要忘记 @Service 注解
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserInfo findUserInfo(String username) {
return userInfoDao.findByUsername(username);
}
}
复制代码
配置 application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
# 声明创建表时使用InnoDB引擎,默认使用myisam
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
复制代码
此时启动应用会在数据库中创建 user_info 表,接着需要初始化 user_info 表中的数据在 init 包下面创建 JDBCInit 类
@Component
public class JDBCInit {
@Autowired
private UserInfoDao userInfoDao;
@PostConstruct
public void init(){
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
UserInfo user1 = new UserInfo();
user1.setUsername("IronMan");
user1.setPassword(passwordEncoder.encode("12345"));
user1.setRole("admin");
userInfoDao.save(user1);
UserInfo user2 = new UserInfo();
user2.setUsername("thor");
user2.setPassword(passwordEncoder.encode("12345"));
user2.setRole("user");
userInfoDao.save(user2);
}
}
复制代码
接着创建 MyUserDetailService,该类继承框架包中的 UserDetailService,作用类似于存储用户角色信息
@Component("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
User user = null;
if (username != null){
userInfo = userInfoService.findUserInfo(username);
if (userInfo != null){
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+userInfo.getRole());
grantedAuthorityList.add(authority);
// 创建User类,框架中的User类
user = new User(userInfo.getUsername(),userInfo.getPassword(),grantedAuthorityList);
}
}
return user;
}
}
复制代码
修改 MyWebSecurityConfig,将保存用信息到内存中的代码全部删除
/**
* prePostEnabled = true表示可以使用@PreAuthorize注解和@PostAuthorize方法级别的注解
*/
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级别的安全控制
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
}
}
复制代码
HelloSecurityController 的代码不变,启动应用,使用 IronMan/12345 登录,可以访问/hello,/admin
使用 thor/12345 登录,访问/hello,/admin
评论