Hello World

依照惯例,我们从一个 Hello World 项目入手。

我们新建了一个名为hello-world的 Gradle 项目。

基于Form 表单的登录认证。

环境

  • Gradle 3.4.1
  • Spring Boot 1.5.2.RELEASE
  • Thymeleaf 3.0.3.RELEASE
  • Thymeleaf Layout Dialect 2.2.0

Gradle Wrapper

修改 Gradle Wrapper 的配置 gradle-wrapper.properties,使用最新的Gradle:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip

build.gradle

1. 自定义依赖的版本

我们可以自定义 Spring Boot 的版本,比如,我们使用了目前最新的 1.5.2.RELEASE 版本。

// buildscript 代码块中脚本优先执行
buildscript {
......
ext {
    springBootVersion = '1.5.2.RELEASE'
}
// 自定义  Thymeleaf 和 Thymeleaf Layout Dialect 的版本
ext['thymeleaf.version'] = '3.0.3.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.0'
......
}

2. 修改项目的名称

修改 build.gradle 文件,让我们的hello-world项目成为一个新的项目。

修改内容也比较简单,修改项目名称及版本即可。

jar {
    baseName = 'hello-world'
    version = '1.0.0'
}

3. 修改项目的仓库地址

为了加快构建速度,我们自定义了一个国内的仓库镜像地址:

repositories {
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public/'
    }
}

4. 指定依赖

// 依赖关系
dependencies {
    // 该依赖对于编译发行是必须的
    compile('org.springframework.boot:spring-boot-starter-web')
    // 添加 Thymeleaf 的依赖
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    // 添加  Spring Security 依赖
    compile('org.springframework.boot:spring-boot-starter-security')
    // 添加   Thymeleaf Spring Security 依赖,与 Thymeleaf 版本一致都是 3.x
    compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')
    // 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ......
    /**
     * 自定义配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll()  // 虽都可以访问
                .antMatchers("/users/**").hasRole("USER")   // 需要相应的角色才能访问
                .antMatchers("/admins/**").hasRole("ADMIN")   // 需要相应的角色才能访问
                .and()
            .formLogin()   //基于 Form 表单登录验证
                .loginPage("/login").failureUrl("/login-error");  // 自定义登录界面
    }
    ......
}

这段代码内容很少,但事实上已经做了很多的默认安全验证,包括:

helloworld\_1.png

那么,上述安全设置是如何默认启用的呢?我们观察下 SecurityConfig 所继承的 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter类,其初始化的时候是这样的

http
    .csrf().and()
    .addFilter(new WebAsyncManagerIntegrationFilter())
    .exceptionHandling().and()
    .headers().and()
    .sessionManagement().and()
    .securityContext().and()
    .requestCache().and()
    .anonymous().and()
    .servletApi().and()
    .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
    .logout();

也就是说,它默认的时候就隐式的启用了多安全设置。

再看下其他几个方法:

/**
 * 用户信息服务
 */
@Bean
public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // 在内存中存放用户信息
    manager.createUser(User.withUsername("waylau").password("123456").roles("USER").build());
    manager.createUser(User.withUsername("admin").password("123456").roles("USER","ADMIN").build());
    return manager;
}
/**
 * 认证信息管理
 * @param auth
 * @throws Exception
 */
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService());
}

其含义为:

  • 用户的认证信息是使用 InMemoryUserDetailsManager 来存储在内存中;
  • 我们默认生成了两个用户,一个是“waylau” 是拥有“USER”角色权限;另一个是“admin” 是拥有“USER”以及“ADMIN”角色权限

运行

运行程序,访问http://localhost:8080 页面。 当我们试图访问以下页面时,提示需要用户登录:

  • “用户管理”:对应 “/users” URL;
  • “管理员管理”:对应 “/admins” URL

登录页面:

helloworld\_2.png

使用具有“USER”角色授权的“waylau”用户登录,可以访问“用户管理”页面:

helloworld\_3.png

“waylau”用户登录可以访问“管理员管理”页面时,提示拒绝访问:

helloworld\_4.png

此时,我们换用 “admin”账号登录即可访问该页面。返回首页,我们能看到该用户的权限信息:

helloworld\_5.png

自定义 403 页面

默认的提示拒绝访问页面太丑,我需要自定义一个页面。我们在返回的页面里面提示“拒绝访问!”。

同时,我要在配置类里面,配置重定向的内容:

......
.formLogin()   //基于 Form 表单登录验证
    .loginPage("/login").failureUrl("/login-error") // 自定义登录界面
    .and()
.exceptionHandling().accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面
......

最终效果:

helloworld\_6.png

处理登出

使用WebSecurityConfigurerAdapter时,应用会自动提供注销功能。 默认情况下,访问 URL /logout来注销用户。注销动作,做了以下几个事情:

  • 使 HTTP 会话无效
  • 清除配置了 RememberMe 身份认证的信息
  • 清除 SecurityContextHolder
  • 重定向到/login?logout

与配置登录功能类似,我们也可以使用各种选项进一步自定义登出要求,比如:

http.logout().logoutSuccessUrl("/");   // 成功登出后,重定向到 首页

这样,成功登出后,重定向到 首页。

其他的还可以选项还有很多:

protected void configure(HttpSecurity http) throws Exception {
    http
        .logout()
            .logoutUrl("/my/logout")                         // 1
            .logoutSuccessUrl("/my/index")
            .logoutSuccessHandler(logoutSuccessHandler)      // 2
            .invalidateHttpSession(true)                     // 3
            .addLogoutHandler(logoutHandler)                 // 4
            .deleteCookies(cookieNamesToClear)               // 5
            .and()
        ...
}
  • (1)自定义触发登出的 URL
  • (2)指定一个自定义LogoutSuccessHandler
  • (3)指定在注销时是否使 HttpSession 无效。默认情况下是 true
  • (4)添加 LogoutHandler。默认情况下,SecurityContextLogoutHandler 作为最后一个 LogoutHandler 被添加
  • (5)允许指定在注销成功时要删除的 Cookie 的名称。这是一个显式添加 CookieClearingLogoutHandler 的快捷方式

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

results matching ""

    No results matching ""