写点什么

Spring 全家桶之 Spring Security(一)

作者:小白
  • 2022 年 8 月 23 日
    上海
  • 本文字数:4906 字

    阅读完需:约 16 分钟

Spring Security

Introduction

  Spring Security 是基于 Spring 的安全框架,Spring Security 提供全面的安全性解决方案,同时在 Web Request 和 Method 处理身份认证和授权,在 Spring Framework 基础上,Spring Security 充分利用了 Soring 的 DI 和 AOP 特性,为应用系统提供了声明式的安全访问控制功能,是一个轻量级的框架,可以很好的与 Spring 及 Spring MVC 集成

核心功能

  1. 认证(Who are you?)

  2. 授权(What you can do?)

原理

  基于 Servlet Filter AOP 实现认证和授权

Spring Security 最佳实践

使用系统自定义用户及 yml 中自定义的用户进行登录

  1. 创建 Maven 项目

  2. 加入依赖,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 启动类


@SpringBootApplicationpublic class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args); }}
复制代码


4.创建 controller 包及 Contrller 控制器


@RestControllerpublic 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,定义角色访问路径


@RestControllerpublic 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 方法


@Entitypublic 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 注解


@Servicepublic 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.Driverspring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=root
spring.jpa.generate-ddl=truespring.jpa.show-sql=truespring.jpa.database=mysql# 声明创建表时使用InnoDB引擎,默认使用myisamspring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
复制代码


此时启动应用会在数据库中创建 user_info 表,接着需要初始化 user_info 表中的数据在 init 包下面创建 JDBCInit 类


@Componentpublic 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




发布于: 刚刚阅读数: 3
用户头像

小白

关注

QA 2019.08.05 加入

微信号JingnanSJ或者公众号RiemannHypo获取异步和图灵系列书籍

评论

发布
暂无评论
Spring 全家桶之 Spring Security(一)_8月月更_小白_InfoQ写作社区