前言
本系列开坑编写的动机是为了在中文圈为Spring Security的推广、使用舔砖加瓦。因为Spring Security几乎对所有Spring应用都有存在的意义,所以我们第一期就把Spring Security作为我们教程的第一个话题。同期我们也制作了每期10分钟的视频讲解,希望可以弥补仅仅使用文字和图片描述一个技术知识点上给读者带来的困扰。
最后,在本系列编写期间,我们使用的SpringBoot版本为1.5;Security版本为5。如因版本差异导致的无法正常运行和获取期望结果的情况发生,可以通过评论私信给予我们反馈。谢谢。
第一期 快速为Spring Boot应用添加Spring Security
本期的任务清单
- 快速初始化一个Spring Boot项目
- 如何添加基于内存的用户鉴权功能
- 如何添加基于角色的访问控制逻辑
1、快速初始化一个Spring Boot项目
我们推荐初学者如果对Maven的pom.xml编写和spring各种starter依赖不熟悉的情况下,使用Spring官方提供的项目初始化工具进行生成。 我们的目的是初始化一个基于SpringBoot的Web应用,并且包含了Security的相关组件。所以在工具的Web界面上依赖组件部分,需要依次键入并选择Web、Security和Thymeleaf三个组件依赖。 点击生成后,浏览器便会自动下载一个项目工程压缩包。
解压缩代码之后,通过IDEA导入后便会得到一个已经包含了Security的基础SpringBoot应用。 运行DemoApplication.java,在控制台看到应用启动完毕后,通过浏览器访问本机的127.0.0.1:8080端口,如看到提示输入用户名和密码的登录对话框,那么就代表了Security已经成功的加载到了应用中。
2、添加基于内存的用户鉴权功能
鉴权和访问控制的区分
首先,我们要对Authentication和Authorization两个词做一个区别。在中文语义上我们会把Authentication称为鉴权,用户认证,通常是一个对根据某些信息、比如用户名、密码、令牌等来辨别用户的过程。而Authorization在中文语义上可以理解为授权、访问控制。为了将来好区别我们将来会统一把Authorization称为访问控制(Access Control),因为Authorization是根据用户的角色、权限等信息来判断目标行为、资源是不是可以被对应的用户使用、消费。 简单的说,鉴权就是用户登录、授权就是权限控制。而我们的第二个任务就是配置Security框架使其可以正确的获取用户信息用于登录检查。 我们选择通过JavaConfig的方式类编写这个配置,类名我们取名叫WebSecurityConfig,并键入下面的代码。
@EnableWebSecurity //配置注解public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //注入新的UserDetailsServiceBean @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("user").password("password").roles("USER").build()); return manager; }}复制代码
我们在代码中主要完成的工作就注入了一个的组件。那么什么是呢?
UserDetailsService 和 UserDetails
UserDetails是我们接触的第一个重要概念。在Spring Security的观点中,UserDetails就好比我们自行设计系统的用户、账户的概念。他包含了用户名、密码和其对应的授予权限。
public interface UserDetails extends Serializable { Collection getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled();}复制代码
UserDetailsSevice就是当前系统中如何获取库存用户信息的服务。
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}}复制代码
在这里就可以简单的认为,在我们输入用户名和密码之后,框架便会通过UserDetailsService 的实现类去寻找验证用户前端输入的用户名和密码是否正确,如果正确则返回UserDetails完成登录操作。Security模式提供了许多种方式的用户信息管理服务实现,比如基于数据库、基于LDAP的。我们当前使用的是最简单基于内存的用户管理实现InMemoryUserDetailsManager。
我们通过新建InMemoryUserDetailsManager,然后通过createUser方法向其添加了一条用户记录。最终将其注入Spring框架,使其为我们的应用在登录时候可以正确的查找到我们期望的用户记录。@Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("user").password("password").roles("USER").build()); return manager; }复制代码
如果使用的是Sping Security5,则在password部分个字符串为
.password("{noop}password")
。否则可能会抛出一个异常。
重启应用后,重新访问http://127.0.0.1:8080/的测试应用,在输入用户名和密码后,则会提示目标访问的页面不存在的404错误。这起码证明登录逻辑已经完成了,稍后简单编写一个简单的控制器和页面模板就可以完成第二个任务。 控制器代码MainController
@Controllerpublic class MainController { @RequestMapping("/") public String root() { return "index"; }复制代码
页面模板代码 index.html
Hello Spring Hello Spring
复制代码
在登陆后便可以看到我们期望的hello页面。
3、 如何添加基于角色的访问控制逻辑
刚刚一个任务我们完成了Spring Security两个主要关注点之一的鉴权功能,现在我们就开始实现一个最简单的访问控制逻辑。 首先,我们先对当前任务的目标做一个简单的设计: 我们将在MainController编写两个路径页面,分别是不需要访问控制的 ""路径和需要登录控制的"\user"路径。
编写控制器
首先我们根据设计将MainContoller的代码补全,添加新的url和对应的视图模板。user.html对先直接复制index.html,只是把内容改成Welcome User就可以了。 MainController .java
@Controllerpublic class MainController { @RequestMapping("/") public String root() { return "index"; } @RequestMapping("/user") public String userIndex() { return "user"; }}复制代码
user.html
Welcome User Welcome User
复制代码
编写访问控制配置
我们的配置类WebSecurityConfig是继承框架提供的,其中有过一个重要的配置方案我们关注下框架提供的默认值实现:
protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }复制代码
这一段代码主要搞事我们一个HttpSecurity的配置主要有三点:
- authorizeRequests()下管理路径访问控制;
- formLogin()管理登录表单配置;
- httpBasic()是否基于Http的验证配置。 后两点不是我们的重点,我们的目标是配置路径的访问控制,所以我们需要在我们自己的配置类覆写这个方法:
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // inde.html对应的url允许所任人访问 .antMatchers("/").permitAll() // user.html对应的url,则需要用户有USER的角色才可以访问 .antMatchers("/user").hasRole("USER") .and() .formLogin(); }复制代码
我们使用了hasRole()基于角色的验证条件,让我再回顾下,之前我们在用户鉴权部分,添加的用户记录的代码是怎么样的?我们添加的user用户其也包含了一个USER的角色,而在访问控制的时候便会检查这一角色授权信息是否匹配。 如果使用的是Spring Security 4 以下的版本则可使用
manager.createUser(User.withUsername("user").password("password").roles("USER").build());复制代码
如使用的是5以上的版本,因为Spring Security中通过配置PasswordEncoder才能进行正确的密码加密操作User.withDefaultPasswordEncoder()
,否则可能框架会报错。
manager.createUser( User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build());复制代码
User.withDefaultPasswordEncoder() 是一个仅仅只能使用在示例应用的方法,因为Spring Security 5中对组件的生成机制进行了调整,使用之前的代码生成用户信息会导致无法判断使用哪个PasswordEncoder。具体的细节在之后单独开一个专题进行说明。 感谢朝歌问弦反馈的问题。
重启应用,首先输入/路径,应用没有提示我们任何登录对话框,我们就看到了Hello Spring的标题。
接着我们再键入\user路径,因为访问控制检查到我们没有完成登录操作,则将我们重定向到login页面完成登录做操 最后,当我们完成用户名和输入之后,因为用户角色与期望配置的一致,我们得以访问目标/user页面。
这样我们也完成最简单的访问控制配置的任务。结尾
我们在本期中对SpringSecurity最重要的两个功能:用户鉴权和访问控制做了最简单的实现和配置。 在下一期,我们将对用户鉴权部分的具体流程展开讲解。让我们下期再见。