Spring Security学习

1.Spring security简介

    Spring security是基于Spring的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring security充分利用依赖注入和面向切面编程功能,为应用系统提供声明式的安全访问控制功能。是一个轻量级的框架。它与Spring MVC有很好的集成。

1.1 Spring security 核心功能

认证

    对于认证来讲,简单的来说就是判断你是谁,对于"你",可以是用户、设备、系统等。

授权(验证)

    对于授权来讲,简单的来说就是判断你能干什么,也叫做权限控制,系统中你能做什么操作。

1.2 Spring security 原理

    简单来讲就是基于Filter链、Servlet、AOP来实现身份认证和权限认证的功能。

2.Demo讲解

2.1 初体验

1.首先创建项目,maven项目即可,手动导入依赖

    <!--指定依赖-->
    <dependencies>
        <!--web开发-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--Spring security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

2.创建启动类

@SpringBootApplication
public class FirstApplication {
    public static void main(String[] args) {
        SpringApplication.run(FirstApplication.class,args);
    }
}

3.创建Controller

@RestController
public class HelloSecurity {

    @RequestMapping("/hello")
    public String hello() {
        return "hello Spring security";
    }
}

4.启动应用,浏览器访问http://localhost:8080/hello
在启动应用时,控制台会输出一个Spring security自动生成的临时密码,用于登录
在这里插入图片描述
5.这时我们可以看到,在浏览器访问后,会有一个登录页面
在这里插入图片描述
6.登录成功后即可看到Controller返回的字符串
在这里插入图片描述

这只是Spring security最简单的应用,登录页面是由Spring security默认生成的,框架在处理请求之前先用AOP做了一个拦截做的身份认证,如果认证成功才能进行请求的处理,随后我们继续往下看

2.1.1 自定义用户名和密码

    需要在SpringBoot配置文件中设置登录的用户名和密码。
在这里插入图片描述
    再次启动项目时即可使用自定义的用户名和密码,也可以看到控制台中并没有生成临时登录密码。

2.1.2 关闭自动验证功能

    关闭自动验证功能后,发送请求后不再验证。

//排除security的配置,使其不启用
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class FirstApplication {
    public static void main(String[] args) {
        SpringApplication.run(FirstApplication.class,args);
    }
}

    重新启动之后即可发现不需自动验证。

2.2 使用内存中的用户信息

    使用WebSecurityConfigurerAdapter控制安全管理的内容。
    继承WebSecurityConfigurerAdapter,重写方法。实现自定义的认证信息。重写方法为该类中定义的configure(AuthenticationManagerBuilder auth)方法。


1.新建配置类继承WebSecurityConfigurerAdapter重写configure(AuthenticationManagerBuilder auth)方法

//表示当前类是一个配置类(相当于是Spring的xml配置文件),在这个类中方法的返回值是java对象,这些对象是放入到Spring容器中的
@Configuration
//表示启用Spring security安全功能
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    //在方法中来配置用户和密码的信息作为登录的数据
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder pe = passwordEncoder();
        auth.inMemoryAuthentication()
                .withUser("zhang3")
                .password(pe.encode("123456"))
                .roles();
        auth.inMemoryAuthentication()
                .withUser("li4")
                .password(pe.encode("000000"))
                .roles();
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(pe.encode("admin"))
                .roles();
    }

    //创建密码加密类
    @Bean
    public PasswordEncoder passwordEncoder() {
        //创建PasswordEncoder对象,实现类是加密算法
        return new BCryptPasswordEncoder();
    }
}

2.需注意在Spring security5版本之后需要对密码加密

2.2.1 基于内存中用户信息角色认证

    基于角色Role的身份认证,同一个用户可能有不同的角色。同时可以开启对方法级别的认证。
1.在配置类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)注解

 @EnableGlobalMethodSecurity:表示启用方法级别的认证
      prePostEnabled:boolean 默认是false
          true:表示可以使用@PreAuthorize注解和@PostAuthorize注解

2.添加角色

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //定义两个角色 normal,admin
        PasswordEncoder pe = passwordEncoder();
        auth.inMemoryAuthentication()
                .withUser("zhang3")
                .password(pe.encode("123456"))
                .roles("normal");
        auth.inMemoryAuthentication()
                .withUser("li4")
                .password(pe.encode("000000"))
                .roles("normal");
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(pe.encode("admin"))
                .roles("admin","normal");
    }

3.方法上添加注解@PreAuthorize

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "使用内存中的信息";
    }

    //指定normal 和 admin 角色都可以访问的方法
    @RequestMapping("/helloUser")
    @PreAuthorize(value = "hasAnyRole('admin','normal')")
    public String helloUser() {
        return "hello 拥有admin和normal的角色";
    }

    @RequestMapping("/helloAdmin")
    @PreAuthorize(value = "hasAnyRole('admin')")
    public String helloAdmin() {
        return "hello 拥有admin的角色";
    }

    @RequestMapping("/helloNormal")
    @PreAuthorize(value = "hasAnyRole('normal')")
    public String helloNormal() {
        return "hello 拥有normal的角色";
    }
}

4.启动项目分别发请求查询结果
在这里插入图片描述
    其他结果就不一一测试了,有需要的自己测试一下。

2.3 基于jdbc的用户认证

1)从数据库中获取用户的身份信息(用户名称,密码,角色)。
2)在Srping security框架对象用户信息的表示类是UserDetailsUserDetails是一个接口,高度抽象的用户信息类。

public interface UserDetails extends Serializable {
	/**
	 * 角色列表,同一个账户可能会有多种角色
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * @return the password
	 */
	String getPassword();

	/**
	 * Returns the username used to authenticate the user
	 */
	String getUsername();

	/**
	 * 账户是否到期
	 */
	boolean isAccountNonExpired();

	/**
	 * 账户是否锁定
	 */
	boolean isAccountNonLocked();

	boolean isCredentialsNonExpired();
	
	boolean isEnabled();
}

3)User类是UserDetails接口的实现类。构造方法有三个参数,分别是UserName、Password和角色列表。

	public User(String username, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

4)我们需要向Spring security提供User对象,这个对象的数据来自数据库的查询。
5)实现UserDetailsService接口并重写UserDetails loadUserByUsername(String username),在方法中获取数据库中的用户信息。

2.3.1 pom
  <!--SpringBoot-->
  <parent>
    <artifactId>spring-boot-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.2.6.RELEASE</version>
  </parent>

  <!--指定依赖-->
  <dependencies>
    <!--web开发-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--Spring security-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <!--jpa-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
  </dependencies>
2.3.2 enity

请手动补齐getter和setter方法

@Entity
public class UserInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    //用户名称
    private String username;
    //密码
    private String password;
    //角色
    private String role;
    
}
2.3.3 service、impl
public interface UserInfoService {
    UserInfo findUserInfo(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    private UserInfoDao userInfoDao;

    @Override
    public UserInfo findUserInfo(String username) {
        UserInfo byUsername = userInfoDao.findByUsername(username);
        return byUsername;
    }
}
2.3.4 dao
public interface UserInfoDao extends JpaRepository<UserInfo,Long> {

    //按照username来查询数据库信息
    UserInfo findByUsername(String username);
}
2.3.5 init
@Component
public class JdbcInit {

    @Autowired
    private UserInfoDao userInfoDao;

    @PostConstruct
    public void init() {
        PasswordEncoder encoder = new BCryptPasswordEncoder();

        UserInfo u = new UserInfo();
        u.setUsername("zhangsan");
        u.setPassword(encoder.encode("000000"));
        u.setRole("normal");

        userInfoDao.save(u);

        UserInfo info = new UserInfo();
        info.setUsername("admin");
        info.setPassword(encoder.encode("admin"));
        info.setRole("admin");
        userInfoDao.save(info);
    }
}
2.3.6 provider
@Component("MyUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserInfoDao dao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = null;
        UserInfo userInfo = null;
        if (username != null) {

            userInfo = dao.findByUsername(username);

            if (userInfo != null) {
                //创建User
                List<GrantedAuthority> list = new ArrayList<>();
                //角色必须以ROLE_开头
                GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + userInfo.getRole());
                list.add(authority);

                //创建User对象
                user = new User(userInfo.getUsername(),userInfo.getPassword(),list);
            }
        }
        return user;
    }
}

以上代码用了jpa,有表会自动生成,没有接触过的可以稍微了解下就可使用。
整体流程是
(Controller -> service -> serviceImpl -> dao) => userInfo
(userInfo.getUsername + userInfo.getPassword + userInfo.getRoles) => user

3.基于角色的权限

3.1 认证和授权

    authentication:认证,认证访问者是谁。一个用户或者一个系统是不是当前要访问的系统中的有效用户。
    authorization:授权,访问者能做什么。


    比如张三用户要访问公司的系统,系统要判断张三是不是系统中的有效用户(认证过程),在是有效用户的基础上,判断张三用户能使用系统中的什么功能(授权过程)。

3.2 RBAC是什么?

    RBAC是基于角色的访问控制,在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予给用户,这样的权限设计很清楚,管理起来很方便。
    其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

  1. 权限:能对资源的操作,比如增加、删除、修改、查看等。
  2. 角色:自定义的,表示权限的集合。一个角色可以有多个权限。
3.2.1 RBAC设计表

1.用户表:用户认证(登录用的表)
    用户名,密码,登陆时间,是否启用等
2.角色表:定义角色信息
    角色名称,角色的描述
3.用户和角色的关系表:角色和用户是多对多的关系
    一个用户可以有多个角色,一个角色也可以有多个用户
4.权限表、角色和权限的关系表
    角色可以有哪些权限

3.3 认证中的接口和类

3.3.1 UserDetails

     UserDetails接口,表示用户信息的

public interface UserDetails extends Serializable {
	/**
	 * 角色列表,同一个账户可能会有多种角色
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * @return the password
	 */
	String getPassword();

	/**
	 * Returns the username used to authenticate the user
	 */
	String getUsername();

	/**
	 * 账户是否到期
	 */
	boolean isAccountNonExpired();

	/**
	 * 账户是否锁定
	 */
	boolean isAccountNonLocked();

	/**
	 * 证书是否过期
	 */
	boolean isCredentialsNonExpired();
	
	/**
	 * 是否启用
	 */
	boolean isEnabled();
}

只有上述四个返回值为boolean类型都为真时,才能正确登录账号

3.3.2 User

    User类是UserDetails接口的实现类。
    里面包含两个构造方法,传参与接口一致。

	public User(String username, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

	public User(String username, String password, boolean enabled,
			boolean accountNonExpired, boolean credentialsNonExpired,
			boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {

		if (((username == null) || "".equals(username)) || (password == null)) {
			throw new IllegalArgumentException(
					"Cannot pass null or empty values to constructor");
		}

		this.username = username;
		this.password = password;
		this.enabled = enabled;
		this.accountNonExpired = accountNonExpired;
		this.credentialsNonExpired = credentialsNonExpired;
		this.accountNonLocked = accountNonLocked;
		this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
	}

    可以自定义类实现UserDetails接口,作为系统中的用户类。这个类可以交给Spring security使用。

3.3.3 UserDetailsService

    这个接口的主要作用是获取用户的信息,得到的是UserDetails对象,一般项目中,都需要自定义类来实现UserDetailsService接口从数据库中获取数据,一个方法需要实现。

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

    根据用户名称,获取用户信息。
    也可不自定义实现类,使用Spring security提供的实现类。

3.3.4 UserDetailsManager

    UserDetailsService定义了根据用户名获取用户详情的能力,UserDetailsManager对其功能进行了拓展。

public interface UserDetailsManager extends UserDetailsService {

	/**
	 * Create a new user with the supplied details.
	 */
	void createUser(UserDetails user);

	/**
	 * Update the specified user.
	 */
	void updateUser(UserDetails user);

	/**
	 * Remove the user with the given login name from the system.
	 */
	void deleteUser(String username);

	/**
	 * Modify the current user's password. This should change the user's password in the
	 * persistent user repository (datbase, LDAP etc).
	 *
	 * @param oldPassword current password (for re-authentication if required)
	 * @param newPassword the password to change to
	 */
	void changePassword(String oldPassword, String newPassword);

	/**
	 * Check if a user with the supplied login name exists in the system.
	 */
	boolean userExists(String username);

}
  • 创建用户账号 : void createUser(UserDetails user)
  • 更新用户账号 : void updateUser(UserDetails user)
  • 删除用户账号 : void deleteUser(String username)
  • 修改用户账号密码 : void changePassword(String oldPassword, String newPassword)
  • 判断用户账号是否存在 : boolean userExists(String username)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值