SpringBoot + Spring Security + Thymeleaf 实现权限管理登录

本文通过一个登录的例子介绍 SpringBoot + Spring Security + Thymeleaf 权限管理。

一、数据库 用户登录账号是 admin,saysky,lockeduser
密码都是 123456

1、表结构
user 表


authority 表


user_authority 表



2、数据
user 表


authority 表


user_authority 表


3、SQL 代码

  1. SET NAMES utf8;
  2. SET FOREIGN_KEY_CHECKS = 0;

  3. -- ----------------------------
  4. --Table structure for `authority`
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `authority`;
  7. CREATE TABLE `authority` (
  8. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  9. `name` varchar(255) NOT NULL,
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

  12. -- ----------------------------
  13. --Records of `authority`
  14. -- ----------------------------
  15. BEGIN;
  16. INSERT INTO `authority` VALUES ('1', 'ROLE_ADMIN'), ('2', 'ROLE_USER');
  17. COMMIT;


  18. -- ----------------------------
  19. --Table structure for `user`
  20. -- ----------------------------
  21. DROP TABLE IF EXISTS `user`;
  22. CREATE TABLE `user` (
  23. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  24. `username` varchar(20) NOT NULL,
  25. `password` varchar(100) NOT NULL,
  26. `name` varchar(20) NOT NULL,
  27. `email` varchar(50) NOT NULL,
  28. `avatar` varchar(200) DEFAULT NULL,
  29. `create_time` datetime DEFAULT NULL,
  30. `last_login_time` datetime DEFAULT NULL,
  31. `status` varchar(10) DEFAULT NULL,
  32. PRIMARY KEY (`id`),
  33. UNIQUE KEY `UK_ob8kqyqqgmefl0aco34akdtpe` (`email`),
  34. UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
  35. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

  36. -- ----------------------------
  37. --Records of `user`
  38. -- ----------------------------
  39. BEGIN;
  40. INSERT INTO `user` VALUES ('1', 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '管理员', 'admin@liuyanzhao.com', null, null, null, 'normal'), ('2', 'saysky', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '言曌', '847064370@qq.com', null, null, null, 'normal'), ('3', 'lockuser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '锁定账号', 'locked@qq.com', null, null, null, 'locked');
  41. COMMIT;

  42. -- ----------------------------
  43. --Table structure for `user_authority`
  44. -- ----------------------------
  45. DROP TABLE IF EXISTS `user_authority`;
  46. CREATE TABLE `user_authority` (
  47. `user_id` bigint(20) NOT NULL,
  48. `authority_id` bigint(20) NOT NULL,
  49. KEY `FKgvxjs381k6f48d5d2yi11uh89` (`authority_id`),
  50. KEY `FKpqlsjpkybgos9w2svcri7j8xy` (`user_id`),
  51. CONSTRAINT `FKgvxjs381k6f48d5d2yi11uh89` FOREIGN KEY (`authority_id`) REFERENCES `authority` (`id`),
  52. CONSTRAINT `FKpqlsjpkybgos9w2svcri7j8xy` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
  53. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  54. -- ----------------------------
  55. --Records of `user_authority`
  56. -- ----------------------------
  57. BEGIN;
  58. INSERT INTO `user_authority` VALUES ('1', '1'), ('2', '2'), ('1', '2');
  59. COMMIT;

  60. SET FOREIGN_KEY_CHECKS = 1;

二、Maven 依赖 【SpringBoot + Spring Security + Thymeleaf 实现权限管理登录】pom.xml
  1. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. 4.0.0

  3. com.liuyanzhao
  4. chuyun
  5. 0.0.1-SNAPSHOT
  6. war

  7. chuyun
  8. Chuyun Blog for Spring Boot


  9. org.springframework.boot
  10. spring-boot-starter-parent
  11. 1.5.9.RELEASE

  12. UTF-8
  13. UTF-8
  14. 1.8
  15. 3.0.3.RELEASE
  16. 2.0.0
  17. 2.4.4



  18. org.springframework.boot
  19. spring-boot-starter-web

  20. org.projectlombok
  21. lombok
  22. true

  23. org.springframework.boot
  24. spring-boot-starter-test
  25. test

  26. org.springframework.boot
  27. spring-boot-starter-thymeleaf

  28. org.springframework.boot
  29. spring-boot-starter-data-jpa

  30. mysql
  31. mysql-connector-java

  32. org.springframework.boot
  33. spring-boot-devtools
  34. true

  35. org.springframework.boot
  36. spring-boot-starter-data-elasticsearch
  37. net.java.dev.jna
  38. jna
  39. 4.3.0

  40. org.springframework.boot
  41. spring-boot-starter-security
  42. org.thymeleaf.extras
  43. thymeleaf-extras-springsecurity4
  44. 3.0.2.RELEASE


  45. com.alibaba
  46. fastjson
  47. 1.2.7

  48. com.github.penggle
  49. kaptcha
  50. 2.3.2


  51. org.springframework.boot
  52. spring-boot-maven-plugin
  53. true



主要需要 SpringBoot、Thymeleaf、Spring Security 的依赖

三、实体类 User.java
  1. package com.liuyanzhao.chuyun.entity;


  2. import lombok.Data;
  3. import org.hibernate.validator.constraints.Email;
  4. import org.hibernate.validator.constraints.NotEmpty;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  9. import org.springframework.security.crypto.password.PasswordEncoder;

  10. import javax.persistence.*;
  11. import javax.validation.constraints.Size;
  12. import java.io.Serializable;
  13. import java.util.ArrayList;
  14. import java.util.Collection;
  15. import java.util.Date;
  16. import java.util.List;

  17. /**
  18. * @author 言曌
  19. * @date 2017/12/28 上午9:06
  20. */

  21. @Entity // 实体
  22. @Data
  23. public class User implements UserDetails, Serializable {

  24. private static final long serialVersionUID = 1L;

  25. @Id // 主键
  26. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
  27. private Long id; // 用户的唯一标识

  28. @NotEmpty(message = "昵称不能为空")
  29. @Size(min=2, max=20)
  30. @Column(nullable = false, length = 20) // 映射为字段,值不能为空
  31. private String name;

  32. @NotEmpty(message = "邮箱不能为空")
  33. @Size(max=50)
  34. @Email(message= "邮箱格式不对" )
  35. @Column(nullable = false, length = 50, unique = true)
  36. private String email;

  37. @NotEmpty(message = "账号不能为空")
  38. @Size(min=3, max=20)
  39. @Column(nullable = false, length = 20, unique = true)
  40. private String username; // 用户账号,用户登录时的唯一标识

  41. @NotEmpty(message = "密码不能为空")
  42. @Size(max=100)
  43. @Column(length = 100)
  44. private String password; // 登录时密码

  45. @Column(length = 200)
  46. private String avatar; // 头像图片地址

  47. private Date createTime;

  48. private Date lastLoginTime;

  49. @Column(length = 10)
  50. private String status;

  51. @ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
  52. @JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
  53. inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
  54. private List authorities;

  55. protected User() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用
  56. }

  57. public User(String name, String email,String username,String password) {
  58. this.name = name;
  59. this.email = email;
  60. this.username = username;
  61. this.password = password;
  62. }



  63. public Collection getAuthorities() {
  64. //需将 List 转成 List,否则前端拿不到角色列表名称
  65. List simpleAuthorities = new ArrayList<>();
  66. for(GrantedAuthority authority : this.authorities){
  67. simpleAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
  68. }
  69. return simpleAuthorities;
  70. }

  71. public void setAuthorities(List authorities) {
  72. this.authorities = authorities;
  73. }


  74. public void setEncodePassword(String password) {
  75. PasswordEncoderencoder = new BCryptPasswordEncoder();
  76. String encodePasswd = encoder.encode(password);
  77. this.password = encodePasswd;
  78. }

  79. @Override
  80. public boolean isAccountNonExpired() {
  81. return true;
  82. }

  83. @Override
  84. public boolean isAccountNonLocked() {
  85. return true;
  86. }

  87. @Override
  88. public boolean isCredentialsNonExpired() {
  89. return true;
  90. }

  91. @Override
  92. public boolean isEnabled() {
  93. return true;
  94. }


  95. }

Authority.java
  1. package com.liuyanzhao.chuyun.entity;

  2. import org.springframework.security.core.GrantedAuthority;

  3. import javax.persistence.*;

  4. /**
  5. * 权限
  6. * @author 言曌
  7. * @date 2018/1/26 下午2:05
  8. */
  9. @Entity
  10. public class Authority implements GrantedAuthority {

  11. private static final long serialVersionUID = 1L;

  12. @Id // 主键
  13. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
  14. private Long id; // 用户的唯一标识

  15. @Column(nullable = false) // 映射为字段,值不能为空
  16. private String name;

  17. public Long getId() {
  18. return id;
  19. }

  20. public void setId(Long id) {
  21. this.id = id;
  22. }


  23. @Override
  24. public String getAuthority() {
  25. return name;
  26. }

  27. public void setName(String name) {
  28. this.name = name;
  29. }

  30. }

四、Dao 层 UserRepository.java
  1. package com.liuyanzhao.chuyun.repository;

  2. import com.liuyanzhao.chuyun.entity.User;
  3. import org.springframework.data.jpa.repository.JpaRepository;


  4. /**
  5. * 用户repository
  6. * @author 言曌
  7. * @date 2017/12/27 0027 20:50
  8. */

  9. public interface UserRepository extends JpaRepository {

  10. /**
  11. * 根据用户名查找用户
  12. * @param username
  13. * @return
  14. */
  15. User findByUsername(String username);

  16. }


五、Service 层 CustomUserService.java
  1. package com.liuyanzhao.chuyun.service;

  2. import com.liuyanzhao.chuyun.entity.User;
  3. import com.liuyanzhao.chuyun.repository.UserRepository;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.security.authentication.LockedException;
  6. import org.springframework.security.core.userdetails.UserDetailsService;
  7. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  8. import org.springframework.stereotype.Service;

  9. /**
  10. * @author 言曌
  11. * @date 2018/1/30 下午8:37
  12. */

  13. @Service
  14. public class CustomUserService implements UserDetailsService{

  15. @Autowired
  16. private UserRepository userRepository;

  17. @Override
  18. public User loadUserByUsername(String username) throws UsernameNotFoundException {
  19. User user = userRepository.findByUsername(username);
  20. if (user == null) {
  21. throw new UsernameNotFoundException("用户名不存在");
  22. } else if("locked".equals(user.getStatus())) { //被锁定,无法登录
  23. throw new LockedException("用户被锁定");
  24. }
  25. return user;
  26. }
  27. }

六、Spring Security 配置 SecurityConfig.java
  1. package com.liuyanzhao.chuyun.config;


  2. import com.liuyanzhao.chuyun.service.CustomUserService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.security.authentication.AuthenticationProvider;
  6. import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
  7. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  8. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  13. import org.springframework.security.crypto.password.PasswordEncoder;


  14. /**
  15. * 安全配置类
  16. *
  17. * @author 言曌
  18. * @date 2018/1/23 上午11:37
  19. */
  20. @EnableWebSecurity
  21. @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法安全设置
  22. public class SecurityConfig extends WebSecurityConfigurerAdapter {

  23. private static final String KEY = "liuyanzhao.com";

  24. @Autowired
  25. private PasswordEncoder passwordEncoder;

  26. @Bean
  27. CustomUserService customUserService() {
  28. return new CustomUserService();
  29. }

  30. @Bean
  31. public PasswordEncoder passwordEncoder() {
  32. return new BCryptPasswordEncoder(); // 使用 BCrypt 加密
  33. }

  34. @Bean
  35. public AuthenticationProvider authenticationProvider() {
  36. DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
  37. authenticationProvider.setUserDetailsService(customUserService());
  38. authenticationProvider.setPasswordEncoder(passwordEncoder); // 设置密码加密方式
  39. return authenticationProvider;
  40. }

  41. /**
  42. * 自定义配置
  43. */
  44. @Override
  45. protected void configure(HttpSecurity http) throws Exception {
  46. http.authorizeRequests().antMatchers("/","/css/**", "/js/**", "/fonts/**","/users").permitAll() // 都可以访问
  47. .antMatchers("/h2-console/**").permitAll() // 都可以访问
  48. .antMatchers("/admin/**").hasRole("ADMIN") // 需要相应的角色才能访问
  49. .antMatchers("/console/**").hasAnyRole("ADMIN","USER") // 需要相应的角色才能访问
  50. .and()
  51. .formLogin()//基于 Form 表单登录验证
  52. .loginPage("/login").failureUrl("/login?error=true") // 自定义登录界面
  53. .and().rememberMe().key(KEY) // 启用 remember me
  54. .and().exceptionHandling().accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面
  55. http.csrf().ignoringAntMatchers("/h2-console/**"); // 禁用 H2 控制台的 CSRF 防护
  56. http.headers().frameOptions().sameOrigin(); // 允许来自同一来源的H2 控制台的请求
  57. }

  58. /**
  59. * 认证信息管理
  60. *
  61. * @param auth
  62. * @throws Exception
  63. */
  64. @Autowired
  65. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  66. //auth.userDetailsService(userDetailsService);
  67. auth.userDetailsService(customUserService());
  68. auth.authenticationProvider(authenticationProvider());
  69. }

  70. }

七、HTML 页面 首页:http://localhost:8080
登录页面:http://localhost:8080/login
登录错误页面:http://localhost:8080/login?error=true
退出登录:http://localhost:8080/logout

index.html
  1. xmlns:th="http://www.thymeleaf.org"
  2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
  3. content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

  4. 未登录,点击 登录

  • 已登录
  • 登录名:
  • 角色:
  • Username:
  • Password:
  • Email :
  • Name:
  • Status:
  • 拥有的角色:
  • 管理员
  • 用户


  • login.html
    1. xmlns:th="http://www.thymeleaf.org">
    2. 登录页面 - 锐客网

    3. 用户名:
    4. 密 码:
    5. oncontextmenu="return false"
    6. onpaste="return false"/>
    7. 记住我


    八、运行效果 1、访问首页:http://localhost:8080


    2、点击 登录,填写登录信息,不勾选记住我
    账号:admin,密码:123456


    3、登录成功,跳转到首页,显示登录信息


    4、访问 http://localhost:8080/logout,可退出登录


    5、登录错误显示


    这里,登录失败的话(用户名不存在或者密码错误),都是显示Bad credentials


    如果登录被锁定的用户,如用户名为lockeduser



    6、记住我也是有效的,关闭浏览器后,下次启动,依然是登录状态
    记住密码后,我们可以看到浏览器里添加了 一条 cookie

      推荐阅读