【SpringSecurity】SpringSecurity2.7.x 的使用(03)

3. 登录流程源码分析

在这里插入图片描述
通过源码我们发现,InmermoryUserDetailManger中的loadUserByUsername方法最核心的。而且该类实现UserDetailService接口。 那么我们就可以自定义一个类并实现该接口UserDetailService。而且我们自定义的类也要交于容器来管理。

4. 模仿从数据库中获取用户

  1. 编写配置类
package com.ykq.springsecurity04.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

/**
 * @program: springsecurity04
 * @description:
 * @author: 闫克起2
 * @create: 2022-11-01 15:11
 **/
@Configuration
public class MySpringSecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //定义表单规则以及过滤规则
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
          //表单的规则
         http.formLogin()
                 .loginPage("/login.html")
                  //该路径没有对应的控制层 交于springsecurity完成登录功能
                 .loginProcessingUrl("/login")
                 .successForwardUrl("/success")
                 .permitAll();
         //禁用跨域伪造
        http.csrf().disable();

        //其他请求必须认证后才能访问
        http.authorizeRequests().anyRequest().authenticated();

        return http.build();

    }
}

注意要将BCryptPasswordEncoder设置为bean,否者不能进行密码验证

  1. 自定义一个实现类实现UserDetailsService 接口,
package com.ykq.springsecurity04.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.Service;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @program: springsecurity04
 * @description:
 * @author: 闫克起2
 * @create: 2022-11-01 15:18
 **/
@Service //该类对象的生命周期交于spring容器管理
public class MyUserService implements UserDetailsService {
    //注入 dao对象---根据账户查询用户信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        /*
            String username, String password,
            boolean enabled 是否可以, boolean accountNonExpired,账户是否没有过期
                boolean credentialsNonExpired, 密码是否没有过期
                boolean accountNonLocked,
                Collection<? extends GrantedAuthority> authorities: 当前账户具有的权限
         */
        if("admin".equals(username)){
            Collection<GrantedAuthority> authorities =new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("user:query"));
            authorities.add(new SimpleGrantedAuthority("user:delete"));
            authorities.add(new SimpleGrantedAuthority("user:update"));
            authorities.add(new SimpleGrantedAuthority("user:insert"));
            User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
                    true,true,true,authorities
                    );
            return user;
        }else if("ykq".equals(username)){
            Collection<GrantedAuthority> authorities =new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("user:query"));
            authorities.add(new SimpleGrantedAuthority("user:export"));
            User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
                    true,true,true,authorities
            );
            return user;
        }
        return null;
    }
}
  1. 从数据库中来获得用户十分简单,只需要实现UserDetailsService 接口,重写loadUserByUsername方法就行了,返回一个UserDetails 类型的数据。
  2. UserDetails 是一个接口,他有一个常用的实现类User,注意User是SpringSecurity包中的。
  3. 用户名和密码十分容易获取,而对于权限信息authorities需要我们想办法从数据库中封装了。

5. 常见授权认证数据库表的设计

在这里插入图片描述

综合案例

使用springboot + thymeleaf+ mybatis+ druid+springsecurity模拟一下从数据库中查询用户,完成授权认证

前后端不分离

1. 引入jar包

    <dependencies>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--数据库连接池druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!--mybatis和springboot整合的依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--thymeleaf和springsecurity的扩展包-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
        <!--thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--security的启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--hutool工具-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2. 配置application.yml文件

mybatis:
  configuration:
    # 开启mybatis日志输出(可以打印出sql执行的日志)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # mybatis的mapper扫描
  mapper-locations: classpath:/mapper/*.xml
spring:
  # 数据源
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 5
      max-active: 20
      max-wait: 3000
      min-idle: 5
      password: 1234
      url: jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai
      username: root
server:
  port: 80

3. 数据库层

  1. 首先创建用户实体类
package com.aaa.springsecuritydemo02.entity;

import lombok.Data;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/11/1 22:40
 * @description :
 */
@Data
public class Users {
    private Integer userid;
    private String username;
    private String userpwd;
    private String sex;
    private String address;
}

为了和security自带的User区分,这里将我们自己的实体类名字叫Users

  1. 创建UserDao及UserDao.xml
@Mapper
public interface UsersDao {

    Users getUserByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.springsecuritydemo02.dao.UsersDao">

    <select id="getUserByUsername" resultType="com.aaa.springsecuritydemo02.entity.Users">
        select *
        from user
        where username = #{username}
    </select>
</mapper>
  1. PermissonDao和PermissonDao.xml

返回UserDetails 的时候需要权限信息,写这个接口是为了根据用户Id查询权限消息

Permisson实体类

@Data
public class Permission {
    private Integer id;
    private String pername;
    private String percode;
}

PermissionDao 接口

@Mapper
public interface PermissionDao {

    /**
     * 根据用户id查询对应的权限
     */
    List<Permission> findPermissionByUserid(Integer userid);
}

PermissionDao.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.springsecuritydemo02.dao.PermissionDao">

    <select id="findPermissionByUserid" resultType="com.aaa.springsecuritydemo02.entity.Permission">
        select distinct p.*
        from user_role ur
                 join role_permission rp
                      on ur.roleid = rp.roleid
                 join permission p on rp.perid = p.perid
        where ur.userid = #{userid}
    </select>
</mapper>

注意sql语句

4. service层

package com.aaa.springsecuritydemo02.service;

import com.aaa.springsecuritydemo02.dao.PermissionDao;
import com.aaa.springsecuritydemo02.dao.UsersDao;
import com.aaa.springsecuritydemo02.entity.Permission;
import com.aaa.springsecuritydemo02.entity.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/11/1 15:41
 * @description :
 */
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersDao usersDao;
    @Autowired
    private PermissionDao permissionDao;

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

        Users user = usersDao.getUserByUsername(username);

        if (user != null) {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            List<Permission> permissions = permissionDao.findPermissionByUserid(user.getUserid());
            //把上面的权限封装authorities中----jdk1.8 Stream流--
			//authorities=permissions.stream()
			//        .map(item->new SimpleGrantedAuthority(item.getPercode()))
			//        .collect(Collectors.toList());

            for (Permission item : permissions) {
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(item.getPercode());
                authorities.add(simpleGrantedAuthority);
            }
            return new User(username, user.getUserpwd(), authorities);
        }

        return null;
    }
}

注意要交给Spring管理 @Service

5. SecurityConfig配置类

package com.aaa.springsecuritydemo02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;


/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/10/29 15:49
 * @description :
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

	@Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //认证
        http
                .formLogin()
                //默认登录页面
                .loginPage("/login.html")
                //登录的处理路径,无需自己创建该路径的业务处理功能。
                .loginProcessingUrl("/login")
                //通过这两个可以修改表单中name的值
                //登录成功跳转路径
                .successForwardUrl("/user/success")

                //放行
                .permitAll();
        //授权
        http
                //禁用跨域请求伪造 csrf
                .csrf().disable();
        http
                .authorizeRequests()
                //其他的请求都需要认证
                .anyRequest().authenticated();


        return http.build();
    }
}

配置文件要配置密码加密器,否者程序不知道要用那种方式来匹配密码

6. 控制层

package com.aaa.springsecuritydemo02.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import java.security.Principal;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/10/31 14:48
 * @description :
 */
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 认证成功后,会把用户的信息封装到Authentication该类中,
     * 并且该类存放在SecurityContext对象中---等价于session
     * @return Authentication
     */
    @GetMapping("/info")
    public Authentication authentication() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        //通过Authentication通常可以拿到Principal,里面存放着用户信息
        //Object principal = authentication.getPrincipal();
        //System.out.println(principal);
        return authentication;
    }

    @PostMapping("/success")
    public ModelAndView success(ModelAndView modelAndView){
        modelAndView.setViewName("success");
        return modelAndView;
    }
}

7. 页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>登录成功页面</title>
</head>
<body>
<a href="#" sec:authorize="hasAuthority('/user/query')">查询用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/delete')">删除用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/update')">修改用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/insert')">添加用户</a><br>
<a href="#" sec:authorize="hasAuthority('/user/export')">导出用户</a><br>
</body>
</html>
  • 注意thymeleaf中要想使用sec语法需要引入thymeleaf-extras-springsecurity5的jar包,页面头部写的
    <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">只是来规定语法,不让报错并能写代码时给出提示,并没有实际作用。真正起作用的是thymeleaf-extras-springsecurity5包。
  • 其实不写头也可以,只是没有语法提示,可能页面会报错,但实际运行不影响
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打乒乓球只会抽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值