spring boot security 通俗理解 + 源码 - Go语言中文社区

spring boot security 通俗理解 + 源码


本文内容适合刚接触spring security的新手,大神请跳过。spring security是一个用来保护spring应用程序的框架,它在用户访问web程序的时候会进行身份的认证(判断当前用户是谁)和授权(当前用户能访问哪些uri,不能访问哪些uri)。我们经常见到场景:1.访问某些网站时需要先登录用户名和密码;2.当你用自己的用户名密码登录某电商网站后,你只能浏览自己的订单,不能看别人的,而电商的某些运营人员或工程师可以看到所有用用户的订单;这个就是认证和授权。接下来我们结合一个Demo来具体看下认证和授权到底是个什么鬼,以及整个过程是怎么操作的(其实只需要重写三个方法就行了)。Demo源码

场景介绍:我们有一个controller,它包含两个方法,一个用来创建用户,另一个用来获取用户。uri如下:

创建用户:/users
获取用户:/users/{username}

当我们在building.gradle文件中添加spring security的依赖时默认security自动开启保护。它做了两件事情,1.访问所有的uri都需要通过认证,没有用过认证的用户不能访问uri,通过认证没有达到该uri访问权限的用户也不能访问该uri;2.spring security自动生成一个用户,它的用户名是user,密码每次web服务启动时才会随机产生。如下图所示:

在这里插入图片描述

这种保护策略当然不是我们想要的,所以我们要自定义保护策略(地球人都是这么干的)。对于上面的两个uri我们规定:创建用户不需要认证和授权,获取用户的时候需要认证和授权。为了实现自定义策略,我们需要继承WebSecurityConfigurerAdapter类,一看名字它就是专门用来配置spring security的。那好我们先来看下它默认的配置长什么样子。代码如下:

protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

这是WebSecurityConfigurerAdapter中的一个方法,它控制每个uri的访问权限,目前默认的配置是每个请求都需要认证,系统支持表单认证和httpbasic两种认证方式。要实现我们的自定一就必须重写这个方法,代码如下:

@Override
  protected void configure(HttpSecurity http) throws Exception {
    http.
        .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/users")
        .permitAll()
        .antMatchers(HttpMethod.GET, "/users/*")
        .hasRole("ADMIN")
        .and()
        .formLogin()
        .and()
        .httpBasic();
  }

permitAll()允许所有人访问/users,而只有具有ADMIN角色的用户才能访问/users/*同样支持表单和httpBasic两种认证方式,这就是spring security的安全性配置。

我们能成功登录电商网站(认证通过)是因为网站里存储了们的账户名和密码,系统拿我们输入的用户名密码跟后台已存储的进行比对,若都一致则登录成功(认证通过)。而此时系统只有一个user用户,密码是随机的,那我们想让用户名:admin,密码:password的用户能通过认证并且能访问/users/{username}该怎么办呢?只需要添加用户存储就可以了,spring security提供了基于内存的用户存储基于数据库的用户存储两种方式。因为是Demo展示,所以本文选择基于内存的用户存储。仍旧是重写方法,代码如下:

@Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .passwordEncoder(new BCryptPasswordEncoder())
        .withUser("admin")
        .password(new BCryptPasswordEncoder().encode("password"))
        .roles("ADMIN")
        .and()
        .withUser("user")
        .password(new BCryptPasswordEncoder().encode("123"))
        .roles("USER");
  }

上述代码在内存中创建了两个用户,信息如下:

用户名 密码 角色
admin password ADMIN
user 123 USER

此时在系统内部就已经存在了两个用户(当重写此方法后spring security就不会再默认生成用户了),当外部输入的用户跟二者一致能匹配上时,认证就通过了。

接下来就是最后一个问题了,认证过程是怎么执行的。当初这块也来回看了好几次,最后是根据别人些的博客外加源码打断调试才搞明白的。简单来说认证过程大多数工作系统代码已经帮我们做了,我们只需要关心UserDetailsUserDetailsService这两个都是接口,前者的实现类User用来记录"找到的"用户的详细信息,后者里面只有一个方法loadUserByUsername,这个方法是用来实现"找" 这个步骤的,接下来机进行详细解释。

UserDetailsService接口:

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

因为Demo是基于内存的用户存储,所以我们来看UserDetailsService的一个实现类InMemoryUserDetailsManager中的loadUserByUsername方法怎么重写的,代码如下:

public class InMemoryUserDetailsManager implements UserDetailsManager,
		UserDetailsPasswordService {
      
private final Map<String, MutableUserDetails> users = new HashMap<>();
      
public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		UserDetails user = users.get(username.toLowerCase());

		if (user == null) {
			throw new UsernameNotFoundException(username);
		}

		return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
				user.isAccountNonExpired(), user.isCredentialsNonExpired(),
				user.isAccountNonLocked(), user.getAuthorities());
	}
}

当应用程序启动时,内存中的那两个用户会用来初始化成员变量users其中key是用户名,value是用户名对应的用户封装成MutableUserDetails(MutableUser是UserDetails的一个实现类)也就是说系统的已存用户都在users中。接着,用户提交的用户名和密码会被封装成Authentication(这是个接口,大多情况下都用它的实现类UsernamePasswordAuthenticationToken这个实现类有两个成员变量principalcredentials,前者用来存储用户名,后者存储密码)通过一系列的逻辑,Authenticationprincipal送到loadUserByUsername方法中,没错,上面的入口参数username就是principal。之后就是根据username在users中找有没有相同的,没有就报异常,有的话就将其封装成User(这也是UserDetails的一个实现类)返回。这就是上面说的"找到了",具体通没通过认证呢?后面还会有个方法再校验User中的password,如果密码匹配则校验才算通过,不过密码校验系统已经替我们把代码写了。

上述是基于内存的的用户认证,所以直接就使用了InMemoryUserDetailsManager,如果是基于mysql的用户认证,可以自己写一个类继承UserDetailsService并实现loadUserByUsername方法根据username从mysql中读取数据再封装成User

Demo中是有一些测试:
测试方法:should_create_user_succeed()是创建用户,不需要认证和授权名,所以直接返回201。
测试方法:should_query_user_succeed()是获取用户, 因为是admin访问,所以请求成功,返回200。
测试方法:should_401_if_query_user_with_Unauthorized_user()是获取用户,因为密码错误,所以未通过认证,返回401。
测试方法:should_403_if_query_user_with_no_admin()是获取用户,认证通过了,但没有ADMIN角色,所以返回403。

就先写到着吧,spring security还有好几个东西,等有时间再一点点补齐。建议看下spring mvc中一个请求的执行过程,理解下拦截器和过滤器,会更加清楚spring security的执行过程。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yy_diego/article/details/100857176
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 21:34:07
  • 阅读 ( 976 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢