天天看点

Spring Security 查询数据库(五)

目录

一、概述

二、搭建自定义UserDetailsService

1. 添加mybatis-plus , 创建数据表,并添加数据

2. 创建数据库表结构

3.  我们创建自定义 MyUserDetailsServiceImpl  

4. SecurityConfig 配置

5. LoginController 页面跳转方法

6. 异常处理信息

三、前端页面配置

1.  login.html 登入页面

2.  main.html 主页面

3.  error.html 错误页面

四、验证效果

五、RememberMe功能实现

六、总结

Spring Security 学习专栏

1. Spring Security 入门学习(一)

2. Spring Security 自定义认证管理器和讲解 (二)

3. Spring Security 接口详解 (三)

4. Spring Security 工作原理 (四)

5. Spring Security 查询数据库(五)

一、概述

下面开始实战使用Spring Security 的实例查询数据库,依然依托上一篇的例子,并在此基础上调整。

通过数据库查询,存储用户和角色实现安全认证

在上一篇的例子中(Spring Security 自定义认证管理器和讲解),我们使用了内存用户角色来演示登录认证。但是实际项目我们肯定是通过数据库完成的。实际项目中,我们可能会有五张表:用户表,角色表,用户角色关联表,权限表,角色权限关联表。本例演示的意义在于:如果我们想在已有项目中增加Spring Security的话,就需要调整登录了。主要是自定义

UserDetailsService

,此外,可能还需要处理密码的问题,因为Spring并不知道我们怎么加密用户登录密码的。这时,我们可能需要自定义PasswordEncoder

(

Spring Security 接口详解

)

二、搭建自定义UserDetailsService

1. 添加mybatis-plus , 创建数据表,并添加数据

继续完善开篇的项目,现在给项目添加

mybatis-plus和数据库驱动

,并使用MySQL数据库。因此在POM文件添加如下jar包

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
    
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>
       <!--Mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
           

在 application.yml文件中加入数据库连接信息:

spring:
  #数据库连接配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://47.103.20.21:3307/zlp-mall?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: zxx1234xx
           

2. 创建数据库表结构

主表:sys_user,sys_role,sys_permission 三张表都是多对多的关系

关联关系表:sys_role_permission_relation, sys_user_role_relation

DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
  `user_type` int(2) NOT NULL DEFAULT 1 COMMENT '用户类型 (0:管理员;1:普通用户)',
  `status` int(2) NOT NULL DEFAULT 0 COMMENT '状态(0:正常,1:删除)',
  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `create_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建用户',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '管理用户表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `role_status` int(1) NULL DEFAULT 1 COMMENT '启启用状态:0->启用 ; 1->禁用',
  `sort` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '后台用户角色表' ROW_FORMAT = Dynamic;


DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `pid` bigint(20) NULL DEFAULT NULL COMMENT '父级权限id',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `permission_value` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限值',
  `icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
  `type` int(1) NULL DEFAULT NULL COMMENT '权限类型:0->目录;1->菜单;2->按钮(接口绑定权限)',
  `data_type` int(1) NULL DEFAULT 0 COMMENT '权限数据类型:0->查看全部;1->查看自己数据权限',
  `url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '前端资源路径',
  `permission_status` int(1) NOT NULL DEFAULT 1 COMMENT '启用状态;0->启用;1->禁用',
  `sort` int(4) NULL DEFAULT NULL COMMENT '排序',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建用户',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_user` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建用户',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_pid`(`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '后台用户权限表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NULL DEFAULT NULL,
  `role_id` bigint(20) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '后台用户和角色关系表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NULL DEFAULT NULL,
  `permission_id` bigint(20) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 793 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '后台用户角色和权限关系表' ROW_FORMAT = Dynamic;
           

3.  我们创建自定义 MyUserDetailsServiceImpl  

前面我们提到过,UserDetailsService,Spring Security在认证过程中需要查找用户,会调用UserDetailsService的loadUserByUsername方法得到一个UserDetails,下面我们来实现他。代码如下:

@Service("userDetailsService")
@Slf4j(topic = "MyUserDetailsServiceImpl")
public class MyUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        log.info("loadUserByUsername.req username={}",username);
        //  1. 查询用户
        User user = userService.loadUserByUsername(username);
        if (Objects.isNull(user)) {
            log.error("User [" + username + "] was not found in db");
            throw new UsernameNotFoundException("User [" + username + "] was not found in db");
        }
        // 密码
        String password = user.getPassword();
        // 2. 设置权限角色信息
        List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList
                ("ADMIN,ROLE_abc");
        return new org.springframework.security.core.userdetails.User(username,password, grantedAuthorities);

    }
}
           

4. SecurityConfig 配置

设置自定义的userDetailsService,密码采用PasswordEncoder密码解析器

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private MyAccessDeniedHandler accessDeniedHandler;

    /** 
     * 认证管理器配置
     * @param auth
     * @date: 2021/3/11 17:39
     * @return: void 
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 设置自定义的userDetailsService
        auth.userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
    }

    /**
     * 密码加密
     * @date: 2021/3/27 18:31
     * @return: org.springframework.security.crypto.password.PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

       // 配置请求地址的权限
        http.authorizeRequests()
                // 所有用户可访问,不需要登入
                .antMatchers("/test/echo").permitAll()
                // 设置前端页面
                .antMatchers("/login.html").permitAll()
                // 需要 ADMIN 权限
//                .antMatchers("/test/admin","/test/getUserInfo").hasAuthority("ADMIN")
                // 需要 ROLE_abc 角色,要去掉ROLE_前缀,不然会报错,SpringSecurity会自动拼接ROLE_前缀
                .antMatchers("/test/admin","/test/getUserInfo").hasRole("abc")
                // 需要 NORMAL 权限
                .antMatchers("/test/normal","/test/getUserInfo").hasAuthority("NORMAL") // 需要 NORMAL 角色。
                // 任何请求,访问的用户都需要经过认证
                .anyRequest().authenticated();

        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler);
       // 设置 Form 表单登陆
        http.formLogin()
                //自定义登录页面
                .loginPage("/showLogin")
                //必须和表单提交的接口一样,会去执行自定义登录逻辑
                .loginProcessingUrl("/login")
                //登录成功后跳转页面,POST请求
                .successForwardUrl("/toMain")
                //登录失败后跳转页面,POST请求
                .failureForwardUrl("/toError")
                .permitAll();

        // 禁止跨域
        http.csrf().disable();
    }
}
           

5. LoginController 页面跳转方法

@Controller
public class LoginController {

    /**
     * 页面跳转
     * @return
     */
    @GetMapping("/showLogin")
    public String showLogin(){
        return "login.html";
    }


    /**
     * 成功后跳转页面
     */
    @PostMapping("/toMain")
    @PreAuthorize("hasRole('ROLE_abc')")
    public String toMain() {
        return "redirect:/main.html";
    }

    /**
     * 失败后跳转页面
     * @return
     */
    @PostMapping("/toError")
    public String toError() {
        return "redirect:/error.html";
    }
}
           

6. 异常处理信息

     自定义 MyAccessDeniedHandler 实现 AccessDeniedHandler 代码如下

代码没有生效,也不知道什么原因???

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request,
					   HttpServletResponse response,
					   AccessDeniedException e) throws IOException{

		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json");
		response.getWriter().println(JSONUtil.parse(Result.forbidden(e.getMessage())));
		response.getWriter().flush();
	}
}
           

就改成全局捕获异常代码如下

/**
 * 全局异常处理器
 * @author admin
 *
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

	
	/**
	 * 处理AccessDeineHandler无权限异常
	 * @param exception
	 * @return
	 */
	@ExceptionHandler(value = AccessDeniedException.class)
	public Result exceptionHandler(AccessDeniedException exception){
		log.error("不允许访问!原因是:",exception.getMessage());
		return Result.forbidden(exception.getMessage());
	}

}
           

三、前端页面配置

1.  login.html 登入页面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    记住我:<input type="checkbox" name="remember-me" value="true" hidden/><br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>
           

2.  main.html 主页面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登陆成功!!!
<a href="/logout" target="_blank" rel="external nofollow" >退出</a>
<a href="main1.html" target="_blank" rel="external nofollow" >跳转</a>
</body>
</html>
           

3.  error.html 错误页面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    操作失败,请重新登录 <a href= "/login.html">跳转</a>
</body>
</html>
           

四、验证效果

上面我们自定义了

userDetailsService

,此时,Spring Security 在其作用流程中会调用自定义登录页面,会从新通过LoginController调用showLogin方法重定向login.html登入页面

Spring Security 查询数据库(五)

我们输入用户名(admin )和密码(admin)点击登入跳转 main.html页面

Spring Security 查询数据库(五)

当我在toMain方法中添加 @PreAuthorize("hasRole('ROLE_abc2')") 方法,因为admin方法没有ROLE_abc2

Spring Security 查询数据库(五)

五、RememberMe功能实现

Spring Security 中 Remember Me 为“记住我”功能,用户只需要在登录时添加 remember-me复选框,取值为true。Spring Security 会自动把用户信息存储到数据源中,以后就可以不登录进行访问。

编写配置

/**
 * 记住我配置类
 * @author zhoubin
 * @since 1.0.0
 */
@Configuration
public class RememberMeConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new
                JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动建表,第一次启动时需要,第二次启动时注释掉
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}
           

修改SecurityConfig.java

http.rememberMe()
                //失效时间,单位秒
                .tokenValiditySeconds(600)
                //登录逻辑交给哪个对象
                .userDetailsService(userDetailsService)
                // 持久层对象
                .tokenRepository(persistentTokenRepository);
           

在客户端页面添加复选框

在客户端登录页面中添加 remember-me 的复选框,只要用户勾选了复选框下次就不需要进行登录了。

<form action="/login" method="post">
 用户名:<input type="text" name="username" /><br/>
 密 码:<input type="password" name="password" /><br/>
 记住我:<input type="checkbox" name="remember-me" value="true"/><br/>
  <input type="submit" value="登录" />
</form>
           

六、总结

本篇内容,我们通过一个小例子开始介绍了如何给web应用引入Spring Security保护;在展示了http-basic验证之后,我们使用了内存用户实验了“角色-资源”访问控制;然后我们介绍了spring security的一些核心概念;之后我们介绍了spring security 是通过filter的形式在web应用中发生作用的,并列举了filter列表,介绍了入口filter,介绍了springboot是如何载入spring security入口filter的。最后我们通过两个实战中的例子展示了spring security的使用。

Spring Security 功能也非常强大,但是还是挺复杂的,本篇内容如有差错还请指出,谢谢!

项目地址

security-example-03 这个模块

https://gitee.com/gaibianzlp/spring-security-demo.git

继续阅读