在这里咱们做个springboot+springSecurity+thymeleaf的整合,才用的框架是springboot+mybatisPlus,所有的用户数据,角色数据,权限数据均来自于数据库。
1 项目结构和数据库结构
为了便于了解整个项目结构,先看下项目结构和数据库结构
1.1 项目结构
1.2 数据库结构
建表和数据语句如下:
CREATE DATABASE /*!32312 IF NOT EXISTS*/`user_db` /*!40100 DEFAULT CHARACTER SET utf8 */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `user_db`;
/*Table structure for table `t_permission` */
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` varchar(32) NOT NULL,
`code` varchar(32) NOT NULL COMMENT '权限标识符',
`description` varchar(64) DEFAULT NULL COMMENT '描述',
`url` varchar(128) DEFAULT NULL COMMENT '请求地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `t_permission` */
insert into `t_permission`(`id`,`code`,`description`,`url`) values ('1','p1','测试资源 1','/r/r1'),('2','p2','测试资源2','/r/r2');
/*Table structure for table `t_role` */
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` varchar(32) NOT NULL,
`role_name` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`status` char(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `t_role` */
insert into `t_role`(`id`,`role_name`,`description`,`create_time`,`update_time`,`status`) values ('1','管理员',NULL,NULL,NULL,'');
/*Table structure for table `t_role_permission` */
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission` (
`role_id` varchar(32) NOT NULL,
`permission_id` varchar(32) NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `t_role_permission` */
insert into `t_role_permission`(`role_id`,`permission_id`) values ('1','1'),('1','2');
/*Table structure for table `t_user` */
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`fullname` varchar(255) NOT NULL COMMENT '用户姓名',
`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
/*Data for the table `t_user` */
insert into `t_user`(`id`,`username`,`password`,`fullname`,`mobile`) values (1,'lisi','$2a$10$aFsOFzujtPCnUCUKcozsHux0rQ/3faAHGFSVb9Y.B1ntpmEhjRtru','李四','123');
/*Table structure for table `t_user_role` */
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`user_id` varchar(32) NOT NULL,
`role_id` varchar(32) NOT NULL,
`create_time` datetime DEFAULT NULL,
`creator` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `t_user_role` */
insert into `t_user_role`(`user_id`,`role_id`,`create_time`,`creator`) values ('1','1',NULL,NULL);
2 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
3 配置application.yml
server:
port: 8080
spring:
thymeleaf:
mode: LEGACYHTML5
main:
allow-bean-definition-overriding: true
datasource:
url: jdbc:mysql://127.0.0.1:3306/user_db?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 5
## 空闲连接存活最大时间,默认600000(10分钟)
idle-timeout: 600000
## 连接池最大连接数,默认是10
maximum-pool-size: 100
## 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
## 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
max-lifetime: 1800000
## 数据库连接超时时间,默认30秒,即30000
connection-timeout: 30000
connection-test-query: SELECT 1
user:
key: SESSION_USER_KEY
4 login.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head><title>用户登录</title></head>
<body>
<form action="/signLogin" method="post">
姓名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br> <input type="submit" value="登录">
</form>
</body>
</html>
5 配置类(config包)
5.1 WebAppConfigurer
该类主要是用于访问路径的重定向,分为两步:
第一步:首先是根目录访问的时候,直接重定向到登录界面。
第二步:然后直接定位到自定义的登录界面。
package com.wuk.config;
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 WebAppConfigurer implements WebMvcConfigurer {
/**
* 默认Url根路径跳转到/login,此url为spring security提供
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//url根路径跳转到/login-view
registry.addViewController("/").setViewName("redirect:/login-view");
// /login-view定位到自定义登录页面
registry.addViewController("/login-view").setViewName("/user/login");
}
}
5.2 WebSecurityConfig
该类是安全配置类,安全配置的内容包括:用户信息、密码编码器、安全拦截机制。
package com.wuk.config;
import org.springframework.context.annotation.Bean;
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;
/**
* @Description:
* @Author: wuk
* @CreateDate: 2020/8/18 14:17
* @UpdateDate: 2020/8/18 14:17
* @UpdateRemark: init
* @Version: 1.0
* @menu
*/
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启注解校验权限
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置安全拦截机制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// .antMatchers("/r/*").authenticated()
//方式1:在这里配置,方式2:在具体接口上配置
// .antMatchers("/r/r1").hasAuthority("p1")
// .antMatchers("/r/r2").hasAuthority("p2")
.anyRequest().permitAll() //其他的统一放行
.and()
.formLogin()
.loginPage("/login-view")
.loginProcessingUrl("/signLogin")
.defaultSuccessUrl("/login/success") //这里只能是defaultSuccessUrl而不能是successForwardUrl 否则报405
.permitAll()
.and()
.csrf().disable();
}
/**
* 配置用户信息服务 第一次写死,后续自动加载自定义的userDetailsService
*/
// @Override
// @Bean
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
// return manager;
// }
/**
* 密码加密方式
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
5.3 MyUserDetailsService
package com.wuk.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wuk.entity.Permission;
import com.wuk.entity.RolePermission;
import com.wuk.entity.User;
import com.wuk.entity.UserRole;
import com.wuk.mapper.PermissionMapper;
import com.wuk.mapper.RolePermissionMapper;
import com.wuk.mapper.UserMapper;
import com.wuk.mapper.UserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Description:
* @Author: wuk
* @CreateDate: 2020/8/22 9:59
* @UpdateDate: 2020/8/22 9:59
* @UpdateRemark: init
* @Version: 1.0
* @menu
*/
@Slf4j
@Component
public class MyUserDetailsService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private RolePermissionMapper rolePermissionMapper;
@Resource
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<User> users = userMapper.selectByMap(new HashMap<String, Object>() {{
put("username", username);
}});
if (CollectionUtils.isEmpty(users)) {
log.info("账号输入错误,查询不到用户");
return null;
}
User user = users.get(0);
List<UserRole> userRoles = userRoleMapper.selectByMap(new HashMap<String, Object>() {{
put("user_id", user.getId());
}});
log.info("userRoles ={}",userRoles);
if (CollectionUtils.isEmpty(userRoles)) {
log.info("该用户尚未分配角色");
return null;
}
List<Long> roleIds = userRoles.stream().map(UserRole::getRoleId).distinct().collect(Collectors.toList());
log.info("roleIds ={}",roleIds);
List<RolePermission> rolePermissions = rolePermissionMapper.selectList(new QueryWrapper<RolePermission>().in("role_id", roleIds));
log.info("rolePermissions ={}",rolePermissions);
if(CollectionUtils.isEmpty(rolePermissions)){
log.info("所有均角色尚未绑定权限");
return null;
}
List<Long> permissionIds = rolePermissions.stream().map(RolePermission::getPermissionId).distinct().collect(Collectors.toList());
log.info("permissionIds ={}",permissionIds);
List<Permission> permissions = permissionMapper.selectBatchIds(permissionIds);
log.info("permissions ={}",permissions);
if(CollectionUtils.isEmpty(permissions)){
log.info("权限不存在");
return null;
}
List<String> codes = permissions.stream().map(Permission::getCode).distinct().collect(Collectors.toList());
String[] perArray = new String[codes.size()];
codes.toArray(perArray);
return org.springframework.security.core.userdetails.User
.withUsername(user.getFullname())
.password(user.getPassword())
.authorities(perArray).build();
}
}
5.4 实体类(entity)
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_permission")
public class Permission {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String code;
private String description;
private String url;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role")
public class Role {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String roleName;
private String description;
private Date updateTime;
private Integer status;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role_permission")
public class RolePermission {
private Long roleId;
private Long permissionId;
}
@Data
@TableName("t_user")
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String fullname;
private String mobile;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user_role")
public class UserRole {
private Long userId;
private Long roleId;
private Date createTime;
private String creator;
}
5.5 mapper
public interface PermissionMapper extends BaseMapper<Permission> {
}
public interface RoleMapper extends BaseMapper<Role> {
}
public interface RolePermissionMapper extends BaseMapper<RolePermission> {
}
public interface UserMapper extends BaseMapper<User> {
}
public interface UserRoleMapper extends BaseMapper<UserRole> {
}
@SpringBootApplication
@MapperScan("com.wuk.mapper")
public class OauthtestApplication {
public static void main(String[] args) {
SpringApplication.run(OauthtestApplication.class, args);
}
}