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启动类
代码语言:javascript复制@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
4.创建controller包及Contrller控制器
代码语言:javascript复制@RestController
public class HelloSecurityController {
@GetMapping("/security")
public String helloSecurity(){
return "Hello Spring Security!";
}
}
5.启动SecurityApplication,控制台会生成密码,请看下图所示
浏览器地址栏输入http://localhost:8080/
输入用户名user及控制台生成的密码,即可登录系统访问HelloSecurityController
如果密码输入错误,则会有相应的提示
6.以上用户名密码都是由系统自动生成的,如果需要自定义用户名密码则需要在配置文件中进行配置,重新启动,输入设置的用户名密码即可登录
代码语言:javascript复制spring:
security:
user:
name: admin
password: admin
7.关闭登录验证对启动类进行修改,{}中可以放入多个配置类
代码语言:javascript复制@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
使用设置在内存中的用户进行登录
  继承WebSecurityConfigurerAdapter,重写configure方法来控制安全管理的内容,将重写的类交由Spring IOC进行管理,可以自定义认证功能,重写是需要使用两个注解@Configuration和@EnableWebSecurity
代码语言:javascript复制@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类,给用户设置角色,代码如下:
代码语言:javascript复制/**
* 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,定义角色访问路径
代码语言:javascript复制@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
代码语言:javascript复制<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方法
代码语言:javascript复制@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)
代码语言:javascript复制public interface UserInfoDao extends JpaRepository<UserInfo,Long>{
//按照username查询数据库
UserInfo findByUsername(String username);
}
创建service层UserInfoService接口,并创建UserInfoServiceImpl实现类实现该接口
代码语言:javascript复制public interface UserInfoService {
UserInfo findUserInfo(String username);
}
不要忘记@Service注解
代码语言:javascript复制@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserInfo findUserInfo(String username) {
return userInfoDao.findByUsername(username);
}
}
配置application.properties
代码语言:javascript复制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类
代码语言:javascript复制@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,作用类似于存储用户角色信息
代码语言:javascript复制@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,将保存用信息到内存中的代码全部删除
代码语言:javascript复制/**
* 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