Spring Boot + Spring Security + Thymeleaf 快速搭建入门

1 前言

Java EE目前比较主流的authorization和authetication的库一个为Spring Security,另一个为apache shiro。由于之前的项目是用spring boot 和shiro + vue实现前后分离,shiro的文档和使用体验比较友好也没有什么需要特别记录的地方,权限控制粒度可以直接精确到方法。至于Spring Security属于Spring的全家桶系列怎么也得体验下。笔者原先用python的flask与php的laravel进行服务端开发,权限管理在python的项目中完全是轻量级的自我实现,收获良多,对于新手的建议先不要一来就上手这种设计复杂的库,对于源码的阅读和设计的理解会有很多障碍,最好先自己实现相应功能后再去学习Shiro与Spring Security的源码设计。 本来不太想写这种记录流水文,但是为了节约大家在Spring Boot + Spring Security + Thymeleaf的搭建时间,现在对搭建基础框架做详细说明,相关代码可以在Github获取。本人包含搭建流程与说明以及搭建过程中踩坑(对框架不够理解就会踩坑)心得。如果跟随文档搭建过程中遇到什么问题请拉到末尾看看友情提示中有无对应的解决办法。笔者尽量在代码注释中说明具体功能。

项目地址: https://github.com/alexli0707/spring_boot_security_themeleaf

2 目的

这是一个Spring Boot + Spring Security + Thymeleaf 的示例项目,我们将使用Spring Security 来进行权限控制。其中/admin/user是两个角色不同权限的访问path。

3 项目

image.png

项目配置文件就不细说,在pom.xml里,为了排除干扰,所有依赖和代码都是最简配置。

3.1 Spring Boot 配置

3.1.1 DefaultController

返回对应路由请求所要访问的模板文件。

package com.walkerlee.example.spring_boot_security_themeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class DefaultController {

    @GetMapping("/")
    public String home1() {
        return "/home";
    }

    @GetMapping("/home")
    public String home() {
        return "/home";
    }

    @GetMapping("/admin")
    public String admin() {
        return "/admin";
    }

    @GetMapping("/user")
    public String user() {
        return "/user";
    }

    @GetMapping("/about")
    public String about() {
        return "/about";
    }

    @GetMapping("/login")
    public String login() {
        return "/login";
    }

    @GetMapping("/403")
    public String error403() {
        return "/error/403";
    }

}

注意这边用的是@Controller不是@RestController二者的区别可以查阅对应文档。

3.1.2 SpringBootSecurityThemeleafApplication

应用启动入口。

package com.walkerlee.example.spring_boot_security_themeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootSecurityThemeleafApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityThemeleafApplication.class, args);
    }
}

3.2 Spring Securtiy 配置

3.2.1 SecurityConfig

继承WebSecurityConfigurerAdapter,目前配置用户帐号密码角色于内存中,在配合db存储使用的时候需要实现org.springframework.security.core.userdetails.UserDetailsService 接口并作更多配置,此处先使用两个保存在内存中的模拟角色帐号进行登录与角色校验。

package com.walkerlee.example.spring_boot_security_themeleaf.security.config;

import org.springframework.beans.factory.annotation.Autowired;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.AccessDeniedHandler;

/**
 * SecurityConfig
 * Description:  Spring Security配置
 * Author:walker lee
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;



    // roles admin allow to access /admin/**
    // roles user allow to access /user/**
    // custom 403 access denied handler
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()  // 允许访问资源
                .antMatchers("/", "/home", "/about").permitAll() //允许访问这三个路由
                .antMatchers("/admin/**").hasAnyRole("ADMIN")   // 满足该条件下的路由需要ROLE_ADMIN的角色
                .antMatchers("/user/**").hasAnyRole("USER")     // 满足该条件下的路由需要ROLE_USER的角色
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);           //自定义异常处理
    }


    // create two users, admin and user
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("user").roles("USER")
                .and()
                .withUser("admin").password("admin").roles("ADMIN");
    }

}

3.2.1 CustomAccessDeniedHandler

出现权限限制返回HTTP_CODE为403的时候自定义展示页面以及内容。

package com.walkerlee.example.spring_boot_security_themeleaf.security.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * CustomAccessDeniedHandler
 * Description:  handle 403 page
 * Author:walker lee
 */
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private static Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        Authentication auth
                = SecurityContextHolder.getContext().getAuthentication();

        if (auth != null) {
            logger.info("User '" + auth.getName()
                    + "' attempted to access the protected URL: "
                    + httpServletRequest.getRequestURI());
        }

        httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
        
        
    }
}

3.3 Thymeleaf + Resources + Static files

3.3.1 thymeleaf的模板文件保存在src/main/resources/templates/文件夹中,具体使用自行google官方文档,Thymeleaf可以支持前后分离的开发方式,具体玩法这边不做过多复述,该demo工程仅使用其中的基础特性,
3.3.2 Fragment

可参照官方文档

3.3.3 Demo中fragment需要特别说明的布局--footer.html,观察sec标签,这是一个与Spring Security协作比较有效的方法,具体可以参照Thymeleaf extra Spring Security
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
</head>
<body>
<div th:fragment="footer">

    <div class="container">

        <footer>
        <!-- this is footer -->
            <span sec:authorize="isAuthenticated()">
                | Logged user: <span sec:authentication="name"></span> |
                Roles: <span sec:authentication="principal.authorities"></span> |
                <a th:href="@{/logout}">Sign Out</a>
            </span>
        </footer>
    </div>

</div>
</body>
</html>

Note
关于Spring Boot的静态资源协议配置可以参照此处 Spring Boot Serving static content

4 运行Demo

4.1 运行命令,访问 http://localhost:8080

$ mvn spring-boot:run

页面

image.png

点击1访问管理员页面,因为没有登录被阻止跳转到登录页:
image.png

输入帐号admin密码admin 登录成功后重定向到http://localhost:8080/admin 。此时返回主页进入2用户页面,提示403没有对应权限:
image.png

最终点击登出重定向到:http://localhost:8080/login?logout,到此快速模板算是搭建完毕,如果需要更多深入的详解和展开这个篇幅肯定是不够的,先到这里。

Tip:聊下可能会遇到的问题

  1. 无法启动项目(如果用的是github上的工程项目肯定是不会的),如果是自己搭建的话可能是没有在pom配置 :
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  2. 登录成功后重定向到你的css文件或者js文件而不是上一次访问的路径? 很有可能是没有配置允许资源文件被外网访问的权限,导致登录成功的回调handler中request的referer是被提示403的资源文件。
  3. 如果需要针对结合数据库以及更多功能的快速集成的话欢迎留个言,元旦有时间也会一并写下。

引用:

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