本文通过一个登录的例子介绍 SpringBoot + Spring Security + Thymeleaf 权限管理。
一、数据库 用户登录账号是 admin,saysky,lockeduser
密码都是 123456
1、表结构
user 表
authority 表
user_authority 表
2、数据
user 表
authority 表
user_authority 表
3、SQL 代码
- SET NAMES utf8;
- SET FOREIGN_KEY_CHECKS = 0;
- -- ----------------------------
- --Table structure for `authority`
- -- ----------------------------
- DROP TABLE IF EXISTS `authority`;
- CREATE TABLE `authority` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
- -- ----------------------------
- --Records of `authority`
- -- ----------------------------
- BEGIN;
- INSERT INTO `authority` VALUES ('1', 'ROLE_ADMIN'), ('2', 'ROLE_USER');
- COMMIT;
- -- ----------------------------
- --Table structure for `user`
- -- ----------------------------
- DROP TABLE IF EXISTS `user`;
- CREATE TABLE `user` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT,
- `username` varchar(20) NOT NULL,
- `password` varchar(100) NOT NULL,
- `name` varchar(20) NOT NULL,
- `email` varchar(50) NOT NULL,
- `avatar` varchar(200) DEFAULT NULL,
- `create_time` datetime DEFAULT NULL,
- `last_login_time` datetime DEFAULT NULL,
- `status` varchar(10) DEFAULT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `UK_ob8kqyqqgmefl0aco34akdtpe` (`email`),
- UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
- ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
- -- ----------------------------
- --Records of `user`
- -- ----------------------------
- BEGIN;
- 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');
- COMMIT;
- -- ----------------------------
- --Table structure for `user_authority`
- -- ----------------------------
- DROP TABLE IF EXISTS `user_authority`;
- CREATE TABLE `user_authority` (
- `user_id` bigint(20) NOT NULL,
- `authority_id` bigint(20) NOT NULL,
- KEY `FKgvxjs381k6f48d5d2yi11uh89` (`authority_id`),
- KEY `FKpqlsjpkybgos9w2svcri7j8xy` (`user_id`),
- CONSTRAINT `FKgvxjs381k6f48d5d2yi11uh89` FOREIGN KEY (`authority_id`) REFERENCES `authority` (`id`),
- CONSTRAINT `FKpqlsjpkybgos9w2svcri7j8xy` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- -- ----------------------------
- --Records of `user_authority`
- -- ----------------------------
- BEGIN;
- INSERT INTO `user_authority` VALUES ('1', '1'), ('2', '2'), ('1', '2');
- COMMIT;
- SET FOREIGN_KEY_CHECKS = 1;
二、Maven 依赖 【SpringBoot + Spring Security + Thymeleaf 实现权限管理登录】pom.xml
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0 com.liuyanzhao chuyun 0.0.1-SNAPSHOT - war
chuyun Chuyun Blog for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE - UTF-8
- UTF-8
1.8 3.0.3.RELEASE 2.0.0 2.4.4 org.springframework.boot spring-boot-starter-web org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java org.springframework.boot spring-boot-devtools true org.springframework.boot spring-boot-starter-data-elasticsearch net.java.dev.jna jna 4.3.0 org.springframework.boot spring-boot-starter-security org.thymeleaf.extras thymeleaf-extras-springsecurity4 3.0.2.RELEASE com.alibaba fastjson 1.2.7 com.github.penggle kaptcha 2.3.2 org.springframework.boot spring-boot-maven-plugin true
三、实体类 User.java
- package com.liuyanzhao.chuyun.entity;
- import lombok.Data;
- import org.hibernate.validator.constraints.Email;
- import org.hibernate.validator.constraints.NotEmpty;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import javax.persistence.*;
- import javax.validation.constraints.Size;
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Date;
- import java.util.List;
- /**
- * @author 言曌
- * @date 2017/12/28 上午9:06
- */
- @Entity // 实体
- @Data
- public class User implements UserDetails, Serializable {
- private static final long serialVersionUID = 1L;
- @Id // 主键
- @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
- private Long id; // 用户的唯一标识
- @NotEmpty(message = "昵称不能为空")
- @Size(min=2, max=20)
- @Column(nullable = false, length = 20) // 映射为字段,值不能为空
- private String name;
- @NotEmpty(message = "邮箱不能为空")
- @Size(max=50)
- @Email(message= "邮箱格式不对" )
- @Column(nullable = false, length = 50, unique = true)
- private String email;
- @NotEmpty(message = "账号不能为空")
- @Size(min=3, max=20)
- @Column(nullable = false, length = 20, unique = true)
- private String username; // 用户账号,用户登录时的唯一标识
- @NotEmpty(message = "密码不能为空")
- @Size(max=100)
- @Column(length = 100)
- private String password; // 登录时密码
- @Column(length = 200)
- private String avatar; // 头像图片地址
- private Date createTime;
- private Date lastLoginTime;
- @Column(length = 10)
- private String status;
- @ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
- @JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
- inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
- private List authorities;
- protected User() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用
- }
- public User(String name, String email,String username,String password) {
- this.name = name;
- this.email = email;
- this.username = username;
- this.password = password;
- }
- public Collection extends GrantedAuthority> getAuthorities() {
- //需将 List 转成 List,否则前端拿不到角色列表名称
- List simpleAuthorities = new ArrayList<>();
- for(GrantedAuthority authority : this.authorities){
- simpleAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
- }
- return simpleAuthorities;
- }
- public void setAuthorities(List authorities) {
- this.authorities = authorities;
- }
- public void setEncodePassword(String password) {
- PasswordEncoderencoder = new BCryptPasswordEncoder();
- String encodePasswd = encoder.encode(password);
- this.password = encodePasswd;
- }
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
Authority.java
- package com.liuyanzhao.chuyun.entity;
- import org.springframework.security.core.GrantedAuthority;
- import javax.persistence.*;
- /**
- * 权限
- * @author 言曌
- * @date 2018/1/26 下午2:05
- */
- @Entity
- public class Authority implements GrantedAuthority {
- private static final long serialVersionUID = 1L;
- @Id // 主键
- @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
- private Long id; // 用户的唯一标识
- @Column(nullable = false) // 映射为字段,值不能为空
- private String name;
- public Long getId() {
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- }
- @Override
- public String getAuthority() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
四、Dao 层 UserRepository.java
- package com.liuyanzhao.chuyun.repository;
- import com.liuyanzhao.chuyun.entity.User;
- import org.springframework.data.jpa.repository.JpaRepository;
- /**
- * 用户repository
- * @author 言曌
- * @date 2017/12/27 0027 20:50
- */
- public interface UserRepository extends JpaRepository
{ - /**
- * 根据用户名查找用户
- * @param username
- * @return
- */
- User findByUsername(String username);
- }
五、Service 层 CustomUserService.java
- package com.liuyanzhao.chuyun.service;
- import com.liuyanzhao.chuyun.entity.User;
- import com.liuyanzhao.chuyun.repository.UserRepository;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.LockedException;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
- /**
- * @author 言曌
- * @date 2018/1/30 下午8:37
- */
- @Service
- public class CustomUserService implements UserDetailsService{
- @Autowired
- private UserRepository userRepository;
- @Override
- public User loadUserByUsername(String username) throws UsernameNotFoundException {
- User user = userRepository.findByUsername(username);
- if (user == null) {
- throw new UsernameNotFoundException("用户名不存在");
- } else if("locked".equals(user.getStatus())) { //被锁定,无法登录
- throw new LockedException("用户被锁定");
- }
- return user;
- }
- }
六、Spring Security 配置 SecurityConfig.java
- package com.liuyanzhao.chuyun.config;
- import com.liuyanzhao.chuyun.service.CustomUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.security.authentication.AuthenticationProvider;
- import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- 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.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- /**
- * 安全配置类
- *
- * @author 言曌
- * @date 2018/1/23 上午11:37
- */
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法安全设置
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- private static final String KEY = "liuyanzhao.com";
- @Autowired
- private PasswordEncoder passwordEncoder;
- @Bean
- CustomUserService customUserService() {
- return new CustomUserService();
- }
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder(); // 使用 BCrypt 加密
- }
- @Bean
- public AuthenticationProvider authenticationProvider() {
- DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
- authenticationProvider.setUserDetailsService(customUserService());
- authenticationProvider.setPasswordEncoder(passwordEncoder); // 设置密码加密方式
- return authenticationProvider;
- }
- /**
- * 自定义配置
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests().antMatchers("/","/css/**", "/js/**", "/fonts/**","/users").permitAll() // 都可以访问
- .antMatchers("/h2-console/**").permitAll() // 都可以访问
- .antMatchers("/admin/**").hasRole("ADMIN") // 需要相应的角色才能访问
- .antMatchers("/console/**").hasAnyRole("ADMIN","USER") // 需要相应的角色才能访问
- .and()
- .formLogin()//基于 Form 表单登录验证
- .loginPage("/login").failureUrl("/login?error=true") // 自定义登录界面
- .and().rememberMe().key(KEY) // 启用 remember me
- .and().exceptionHandling().accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面
- http.csrf().ignoringAntMatchers("/h2-console/**"); // 禁用 H2 控制台的 CSRF 防护
- http.headers().frameOptions().sameOrigin(); // 允许来自同一来源的H2 控制台的请求
- }
- /**
- * 认证信息管理
- *
- * @param auth
- * @throws Exception
- */
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- //auth.userDetailsService(userDetailsService);
- auth.userDetailsService(customUserService());
- auth.authenticationProvider(authenticationProvider());
- }
- }
七、HTML 页面 首页:http://localhost:8080
登录页面:http://localhost:8080/login
登录错误页面:http://localhost:8080/login?error=true
退出登录:http://localhost:8080/logout
index.html
- xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
- content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
- 未登录,点击 登录
- 已登录
- 登录名:
- 角色:
- Username:
- Password:
- Email :
- Name:
- Status:
- 拥有的角色:
- 管理员
- 用户
- 已登录
login.html
- xmlns:th="http://www.thymeleaf.org">
登录页面 - 锐客网 - 用户名:
- 密 码:
- oncontextmenu="return false"
- onpaste="return false"/>
- 记住我
八、运行效果 1、访问首页:http://localhost:8080
2、点击 登录,填写登录信息,不勾选记住我
账号:admin,密码:123456
3、登录成功,跳转到首页,显示登录信息
4、访问 http://localhost:8080/logout,可退出登录
5、登录错误显示
这里,登录失败的话(用户名不存在或者密码错误),都是显示Bad credentials
如果登录被锁定的用户,如用户名为lockeduser
6、记住我也是有效的,关闭浏览器后,下次启动,依然是登录状态
记住密码后,我们可以看到浏览器里添加了 一条 cookie
推荐阅读
- spring-boot-devtools 快速重启的秘密!#yyds干货盘点#
- 可以将Google Appengine的管理控制台集成到spring-boot项目中吗()
- 如何在spring-boot中用application.properties制作config类()
- spring-boot-2.0.3不一样系列之源码篇 - run方法之createApplicationContext,绝对有值得你看的地方
- spring-boot-2.0.3不一样系列之源码篇 - SpringApplication的run方法之SpringApplicationRunListener,绝对有值得你看的地方
- spring-boot集成PageHelper和通用Mapper
- spring-boot application.properties设置最全指南
- spring-boot 中application.properties的各种配置