Spring Security小教程 Vol 1. 快速为Spring Boot添加鉴权授权功能

噗噗虎和阿基拉的废柴大叔小课堂

本文阅读约需要10分钟
本期的配套视频讲解请访问https://www.bilibili.com/video/av45427128/

前言

本系列开坑编写的动机是为了在中文圈为Spring Security的推广、使用舔砖加瓦。因为Spring Security几乎对所有Spring应用都有存在的意义,所以我们第一期就把Spring Security作为我们教程的第一个话题。同期我们也制作了每期10分钟的视频讲解,希望可以弥补仅仅使用文字和图片描述一个技术知识点上给读者带来的困扰。

最后,在本系列编写期间,我们使用的SpringBoot版本为1.5;Security版本为4。如因版本差异导致的无法正常运行和获取期望结果的情况发生,可以通过评论私信给予我们反馈。谢谢。

第一期 快速为Spring Boot应用添加Spring Security

本期的任务清单

  1. 快速初始化一个Spring Boot项目
  2. 如何添加基于内存的用户鉴权功能
  3. 如何添加基于角色的访问控制逻辑

1、快速初始化一个Spring Boot项目

我们推荐初学者如果对Maven的pom.xml编写和spring各种starter依赖不熟悉的情况下,使用Spring官方提供的项目初始化工具进行生成。https://start.spring.io/
我们的目的是初始化一个基于SpringBoot的Web应用,并且包含了Security的相关组件。所以在工具的Web界面上依赖组件部分,需要依次键入并选择Web、Security和Thymeleaf三个组件依赖。
点击生成后,浏览器便会自动下载一个项目工程压缩包。

https://start.spring.io/

解压缩代码之后,通过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;
    }
}

我们在代码中主要完成的工作就注入了一个\color{red}{UserDetailsService}的组件。那么什么是\color{red}{UserDetailsService}呢?

UserDetailsService 和 UserDetails

UserDetails是我们接触的第一个重要概念。在Spring Security的观点中,UserDetails就好比我们自行设计系统的用户、账户的概念。他包含了用户名、密码和其对应的授予权限。

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> 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是Security提供的UserDetailsService的实现类

我们通过新建InMemoryUserDetailsManager,然后通过createUser方法向其添加了一条用户记录。最终将其注入Spring框架,使其为我们的应用在登录时候可以正确的查找到我们期望的用户记录。

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user").password("password").roles("USER").build());
        return manager;
    }

重启应用后,重新访问http://127.0.0.1:8080/的测试应用,在输入用户名和密码后,则会提示目标访问的页面不存在的404错误。这起码证明登录逻辑已经完成了,稍后简单编写一个简单的控制器和页面模板就可以完成第二个任务。
控制器代码MainController

@Controller
public class MainController {
    @RequestMapping("/")
    public String root() {
        return "index";
    }

页面模板代码 index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Hello Spring</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
    <h1>Hello Spring</h1>
</body>
</html>

在登陆后便可以看到我们期望的hello页面。


index.html的访问页面

3、 如何添加基于角色的访问控制逻辑

刚刚一个任务我们完成了Spring Security两个主要关注点之一的鉴权功能,现在我们就开始实现一个最简单的访问控制逻辑。
首先,我们先对当前任务的目标做一个简单的设计:
我们将在MainController编写两个路径页面,分别是不需要访问控制的 ""路径和需要登录控制的"\user"路径。


基于url的访问控制设计图

编写控制器

首先我们根据设计将MainContoller的代码补全,添加新的url和对应的视图模板。user.html对先直接复制index.html,只是把内容改成Welcome User就可以了。
MainController .java

@Controller
public class MainController {
    @RequestMapping("/")
    public String root() {
        return "index";
    }
    @RequestMapping("/user")
    public String userIndex() {
        return "user";
    }
}

user.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Welcome User</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
    <h1>Welcome User</h1>
</body>
</html>

编写访问控制配置

我们的配置类WebSecurityConfig是继承框架提供的\color{red}{WebSecurityConfigurerAdapter},其中有过一个重要的配置方案我们关注下框架提供的默认值实现:


    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的配置主要有三点:

  1. authorizeRequests()下管理路径访问控制;
  2. formLogin()管理登录表单配置;
  3. 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的角色,而在访问控制的时候便会检查这一角色授权信息是否匹配。

manager.createUser(User.withUsername("user").password("password").roles("USER").build());

重启应用,首先输入/路径,应用没有提示我们任何登录对话框,我们就看到了Hello Spring的标题。


\路径

接着我们再键入\user路径,因为访问控制检查到我们没有完成登录操作,则将我们重定向到login页面完成登录做操

最后,当我们完成用户名和输入之后,因为用户角色与期望配置的一致,我们得以访问目标/user页面。


\user页面

这样我们也完成最简单的访问控制配置的任务。

结尾

我们在本期中对SpringSecurity最重要的两个功能:用户鉴权和访问控制做了最简单的实现和配置。
在下一期,我们将对用户鉴权部分的具体流程展开讲解。让我们下期再见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容