Spring-Security-教程(一)---简单的登录认证

一、Spring Security 概述

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准,也是一个专注于向Java应用程序提供身份验证和授权的框架。
连接:Spring Security

博客地址:https://blog.ffspace.cn
项目代码:https://github.com/Bootcap/spring-security-study-session

二、环境要求

  • JDK ≥1.7
  • Maven 3.0+
  • IntelliJ IDEA/eclipse

【提示】: 本文以 IDEA 和 maven 以及Spring Boot 2.0 为例进行教程

三、使用Maven进行构建

在pom.xml文件中引入相关的jar包(不含security)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <name>spring-security-study-session</name>
  <groupId>com.bootcap.session.security</groupId>
  <artifactId>spring-security-study-session</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
  </parent>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  </properties>


  <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

在项目的src/main/resources下进行配置

# 端口号
server:
  port: 8080

spring:
  # thymeleaf配置
  thymeleaf:
    enabled: true
    encoding: UTF-8
    mode: HTML
    servlet:
      content-type: text/html
    prefix: classpath:/templates/
    suffix: .html

四、创建一个不受保护的web页面

Web页面包含两个简单的视图:index主页和“hello”页面,都定义在Thymeleaf模板中。

4.1 Index页面

路径:src/main/resources/templates/index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Security Index</title>
</head>
<body>
    <h1>Index Page</h1>
    <a th:href="@{/hello}">点击前往hello页面</a>
</body>
</html>

【注意】: 使用thymeleaf时需要在<html>标签后面加入xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" 属性,才能被编译器解析为thymeleaf模板

4.2 Hello页面

路径:src/main/resources/templates/hello.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1>Hello Spring Security</h1>
</body>
</html>

从上面index.html可以看到包含了一个"/hello"连接点击跳转到hello.html页面的简单流程

五、配置Spring MVC视图控制器

由于Web应用程序基于Spring MVC。 因此,需要配置视图控制器来暴露这些模板。

路径:src/main/java/com/bootcap/session/security/configuration/TemplateConfig.java

package com.bootcap.session.security.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class TemplateConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }
}

六、配置Spring Boot启动类

6.1 到了这一步,我们需要将项目运行查看是否无异常,才进行下一步操作,因此需要配置spring boot启动类

路径:src/main/java/com/bootcap/session/security/app/Application.java

package com.bootcap.session.security.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"com.bootcap.session.security"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}
6.2 运行main方法,并在浏览器地址栏输入:http://localhost:8080/ 如果看到index.html页面,说明已经成功运行
image
6.3 点击跳转到hello页面链接,无需任何认证即可进行跳转
image

【小窍门】: 为什么要使用@ComponentScan注解:

  • 如果你的其他包都在使用了@SpringBootApplication注解的main方法所在的包及其下级包,则SpringBoot会自动帮你把其他包都扫描。
  • 如果你有一些bean所在的包,不在@SpringBootApplication注解main方法的包及其下级包,那么你需要手动加上@ComponentScan注解并指定要寻找的bean所在的包路径。(详见上述代码中package的不同之处)

七、引入并使用Spring Security

在上述的两个视图中,我们希望在访问"/hello"时需要登录才能够进入Hello页面。此时我们可以通过Spring Security来实现。(如果Spring Security在类路径上,则Spring Boot会使用"Basic"认证自动保护所有HTTP请求,也可以自定义设置)

7.1 pom.xml加入Spring Security

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

7.2 配置只有认证过的用户才能访问hello.html

路径:src/main/java/com/bootcap/session/security/configuration/WebSecurityConfig.java

package com.bootcap.session.security.configuration;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/","/index").permitAll() // permitAll被允许访问
                .anyRequest().authenticated() // 其余的请求需要认证后才可允许访问
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
            .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication() // 在内存中进行身份验证
                .withUser("user")
                .password("password")
                .roles("USER");
    }

}

【说明】:

  • WebSecurityConfig类使用了@EnableWebSecurity注解,以启用Spring Security的Web安全支持。

  • configure(HttpSecurity)方法自定义有哪些url需要被认证,哪些不需要。当用户登录后将会被重定向请求到需要身份认证的页面(hello.html),否则在用户未登录的情况下将会跳转到登录页面

  • configure(AuthenticationManagerBuilder)方法用于设置认证的条件保存于内存中,用户名为“user”,密码为“123456”,角色为User。同时该方法也可以修改认证方式为jdbc进行认证

【注意】: 使用了Spring Boot 2.0以上的Security会存在There is no PasswordEncoder mapped for the id "null"异常,解决方案见底部“常见问题”

7.3 创建登录页面(认证时需要用到)

路径:src/main/resources/templates/login.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<div th:if="${param.error}">
    用户名或密码不正确
</div>
<div th:if="${param.logout}">
    你已经退出登录
</div>
<form th:action="@{/login}" method="post">
    <div><label> 用户名: <input type="text" name="username"/> </label></div>
    <div><label> 密&nbsp;&nbsp;&nbsp;码: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登录"/></div>
</form>
</body>
</html>

【说明】:

  • 该登录页面会将用户名和密码以表单形式提交到"/login"。

  • 在Spring Security提供了一个拦截请求并验证的过滤器,在用户未通过认证的情况下会重定向到"/login?error",并且显示相应的错误信息。注销成功后,Spring Security会将地址重定向到"/login?logout",我们即可在页面中看到相应的登出信息

7.4 修改hello.html

在认证成功后跳转到hello.html页面,我们希望能够看到登录的用户名,同时允许用户退出登录,因此我们需要修改hello.html页面

路径:src/main/resources/templates/hello.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]</h1>
    <form th:action="@{/logout}" method="post">
        <input type="submit" value="退出登录" />
    </form>
</body>
</html>

【说明】:

  • 我们在hello.html页面中使用了HttpServletRequest#getRemoteUser()的thymeleaf集成来显示用户名。

  • 页面中退出登录表单会将请求提交到"/logout",成功注销后程序会重定向到"/login?logout"。

7.5 启动应用程序

通过文章刚开始的 【配置Spring Boot启动类】模块启动应用程序,这里不再过多介绍。

八、测试

8.1 应用启动后, 在浏览器中访问 http://localhost:8080. 就能访问到Index页面
image
8.2 点击跳转到hello.html页面,会发现没有认证不允许进入,程序自动跳转到登录页
image
8.3 我们输入错误的信息会提示用户名或密码不正确,同时地址栏的地址也变成了"/login?error"
image
8.4 下面我们输入正确的账号登录(用户名:user,密码:123456)

我们会发现在控制台出现了异常(解决方案:详见底部【常见问题】)


image
8.5 异常解决后我们再进行测试已经可以正常地跳转到hello.html页面,并显示了登录的用户名
image
8.6 最后我们点击退出登录,即可完成注销操作(此时地址栏"/login?logout",并且跳回登录页面提示"你已经退出登录")
image

九、总结

恭喜!你已经开发了一个简单的Spring Security程序。

常见问题

【问题一】:

在测试中,我们正常登录时候出现了异常:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

【解答】:通过问题我们知道该异常来自认证信息WebSecurityConfig的方法configure(AuthenticationManagerBuilder)。所以我们查阅官方文档,发现在spring boot 2.0之上使用的是Spring Security 5.0版本,同时也指出使用密码校验时候需要进行密码加密。

因此,改动如下:

方法1:在Application.java启动类下加入该方法,关闭密码加密校验.(不推荐)
 @Bean
    public static PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    
方法2:在WebSecurityConfig.java类的configure(AuthenticationManagerBuilder)进行如下改动
 @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//      auth.inMemoryAuthentication() // 在内存中进行身份验证
//              .withUser("user")
//              .password("password")
//              .roles("USER");

        auth.inMemoryAuthentication() // 在内存中进行身份验证
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("USER");
                
//        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

小技巧

在我们开发的过程中,我们经常会遇到修改一点点东西时候,都需要重启操作来生效。因此,spring boot为我们提供了一个devTool热更新工具,无需重启即可生效

pom.xml中引入相关jar
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
</parent>
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
 ... 
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
  </dependency>
...
<dependencies>
如何使用

【提示】: 由于我自己使用的是idea进行开发,因此对eclipse的引入是否需要快捷键并不是很清楚,提供了相关链接

IDEA使用:

  • 修改了java类的地方,使用Ctrl+Shift+F9进行热更新
  • 静态页面/模板页面,使用Ctrl+F9进行热更新
  • 快捷键使用后不生效?前往File-Settings-Compiler-Build Project automatically选项开始idea自动编译

eclipse使用:

附录:HttpSecurity类的常用方法

方法 说明
openidLogin() 基于OpenId验证
headers() 将安全头添加到响应
cors() 跨域配置
sessionManagement() session会话管理
portMapper() 配置一个PortMapper(HttpSecurity#(getSharedObject(class))),供SecurityConfigurer对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() 配置容器预认证,默认为Servlet容器进行管理
x509() 配置x509认证
rememberMe() 配置“记住我”的验证
authorizeRequests() 允许HttpServletRequest限制访问
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
logout() 退出登录。访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?logout”
anonymous() 允许配置匿名用户访问。默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定用于表单身份验证。
oauth2Login() 用于OAuth 2.0 或OpenID的身份验证
requiresChannel() 配置通道安全。
httpBasic() 配置Http Basic验证
addFilterAt() 在指定的Filter类位置添加过滤器

下一篇:Spring Security 入门教程(二) - 基于数据库信息进行验证

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

推荐阅读更多精彩内容