超详细!利用SpringBoot+SpringCloud做一个问答项目(七)

7 篇文章 0 订阅
5 篇文章 4 订阅

目录

一、使用网关验证登录并授权

1.思路

2.网关配置

3.查询某用户的信息

4.根据用户名查询用户的权限信息

5.在业务层实现查询用户详情及权限列表

6.通过用户名获取用户基本信息和权限列表


一、使用网关验证登录并授权

1.思路

网关是服务器的集群中的第一入口,也是唯一入口,应该在这个入口就直接检查用户的登录信息,如果登录成功,授权也应该直接完成,而不是在集群中其它的服务器中进行登录验证和授权,因为如果用户登录信息有误,一开始都不允许执行到集群之内,而是在网关就直接回绝用户的请求!所以,网关straw-gateway项目不仅仅要起到路由转发的作用,还需要完成登录验证和授权。

目前,已经在straw-api-user项目中添加使用Spring Security的代码,这些代码都是练习Spring Security的功能而存在的,先将这些类删除,或使之不可用,例如去除类之前注解:

2.网关配置

首先,需要在straw-gateway项目中添加依赖Spring Security框架,同时,由于验证登录时需要访问数据库(从数据库中查询尝试登录的用户的信息),所以,还需要在straw-gateway项目中添加与数据库编程相关的依赖:

<dependencies>
    <!-- Spring Security:安全框架,用于验证登录、授权访问、密码加密 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- Lombok:通过注解简化开发 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
    <!-- Mybatis Plus:简化Mybatis开发 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <!-- druid:alibaba的数据库连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <!-- Mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- 网关路由 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <!-- Eureka Client -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </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>
</dependencies>

由于添加了数据库的相关依赖,SpringBoot在启动时会自动读取连接数据库的信息,但是,当时straw-gateway项目中并没有这些配置,所以,启动straw-gateway就会报错!需要先在application.properties中补充这些配置(从straw-api-user中复制过来即可):

straw-gateway中处理登录时,必须要从数据库中查询用户信息,也就是处理User类型的数据,而这个类型是在straw-api-user中也需要使用到的,也就是2个项目都需要使用User类型!其实,以后,其它的类也可能出现类似的情况,同一个类在多个不同的子模块项目中都需要被使用!

为了更好的解决这个问题,应该将这些类放在专门的项目中,当其它项目需要使用到这些类时,直接依赖该项目即可!

则创建straw-commons子模块项目,用于存放公共使用的类(创建过程中,不需要勾选任何依赖):

创建成功后,由于当前项目只是用于存放公共使用的类,所以,并不需要独立运行,也不会在其中编写功能性代码,相关的文件是可以删除的:

然后,将straw-api-usermodel包整个的复制到straw-commons项目中:

当把model包复制过来后,其中的类是报错的!因为当前straw-commons项目并没有依赖必要的依赖,所以,还需要调整straw-commonspom.xml文件,修改父级项目,并调整依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.tedu</groupId>
        <artifactId>straw</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>straw-commons</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>straw-commons</name>

    <dependencies>
        <!-- Mybatis Plus:简化Mybatis开发 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!-- Lombok:通过注解简化开发 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

然后,应该在straw-api-userpom.xml中添加依赖straw-commons项目!先在父项目straw中添加对straw-commons的管理:

然后,在straw-api-userpom.xml中添加依赖:

由于straw-api-user通过以上添加依赖就可以使用straw-commons中的User类等相关实体类,则在straw-api-user项目应该将model包删除:

一旦删除,在straw-api-user项目中原本导入UserClassInfo的相关类和接口都会报错,需要将原本错误的import语句删除并重新导入!

然后,在resources\mapper下的各XML文件中的<resultmap>节点也配置了实体类的全名,也需要修改包名部分!

全部完成后,点击Build菜单 中的Rebuild选项,会编译当前项目,是不会出现错误的!

3.查询某用户的信息

straw-gatewaypom.xml中,也添加依赖straw-commons

在使用Spring Security验证登录时,会要求“根据用户名获取用户信息”,所以,在straw-gateway中需要实现该功能,先添加持久层接口与XML文件,这些文件从straw-api-user项目中复制过来,然后:

  • UserMapper.java接口中的findByPhone()方法删除;
  • UserMapper.xml中配置findByPhone()的查询节点删除;
  • 修改UserMapper.xml中根节点的UserMapper接口的名包;

然后,在启动类的声明之前配置持久层接口的包:  

全部完成后,自行创建单元测试所需的文件夹和测试类文件,编写并执行单元测试:

4.根据用户名查询用户的权限信息

由于Spring Security在处理登录时,还会对成功登录的用户进行授权,所以,还需要实现“根据用户名查询该用户的权限的列表”查询功能,需要执行的SQL语句大致是:

SELECT DISTINCT(permission.id), permission.authority, permission.description
FROM permission
LEFT JOIN role_permission ON permission.id = role_permission.permission_id
LEFT JOIN role ON role_permission.role_id = role.id
LEFT JOIN user_role ON role.id = user_role.role_id
LEFT JOIN user ON user_role.user_id = user.id
WHERE user.username='13800138001'
ORDER BY permission.id;

要实现以上查询功能,首先,得准备一个类,用于封装此次的查询结果,则在cn.tedu.straw.gateway.vo包中创建PermissionVO类:

package cn.tedu.straw.gateway.vo;

import lombok.Data;

import java.io.Serializable;

@Data
public class PermissionVO implements Serializable {

    private Integer id;
    private String authority;
    private String description;

}

一般,在项目中,每张表都有对应的实体类,常用于增、改操作,而查询不一定使用实体类,因为实体类与数据表完全对应,如果数据表中有20个字段,则实体类中应该有20个属性,而查询可能并不需要将所有字段的值都查出来,大多数数据表中都存在一些为了管理数据而存在的字段,在常规查询中根本不会使用到!所以,在查询时,通常会与查询功能对应的VO(Value Object)类。

然后,在cn.tedu.straw.gateway.mapper包下创建PermissionMapper接口,用于定义关于“权限”数据的增删改查方法:

package cn.tedu.straw.gateway.mapper;

import cn.tedu.straw.gateway.vo.PermissionVO;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface PermissionMapper {

    /**
     * 查询某用户的权限列表
     *
     * @param username 用户名
     * @return 该用户的权限列表
     */
    List<PermissionVO> findByUsername(String username);

}

然后,在resources/mapper下创建(也可以是复制粘贴得到)PermissionMapper.xml文件,配置以上抽象方法映射的SQL语句:

<?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="cn.tedu.straw.gateway.mapper.PermissionMapper">

    <select id="findByUsername" resultType="cn.tedu.straw.gateway.vo.PermissionVO">
        SELECT DISTINCT(permission.id), permission.authority, permission.description
        FROM permission
        LEFT JOIN role_permission ON permission.id = role_permission.permission_id
        LEFT JOIN role ON role_permission.role_id = role.id
        LEFT JOIN user_role ON role.id = user_role.role_id
        LEFT JOIN user ON user_role.user_id = user.id
        WHERE user.username=#{username}
        ORDER BY permission.id
    </select>

</mapper>

完成后,在testcn.tedu.straw.gateway.mapper下创建PermissionMapperTests测试类,编写并执行单元测试:

package cn.tedu.straw.gateway.mapper;

import cn.tedu.straw.gateway.vo.PermissionVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class PermissionMapperTests {

    @Autowired
    PermissionMapper mapper;

    @Test
    void findByUsername() {
        String username = "13800138001";
        List<PermissionVO> permissions = mapper.findByUsername(username);
        System.err.println("权限数量:" + permissions.size());
        for (PermissionVO permission : permissions) {
            System.err.println("权限:" + permission);
        }
    }

}

5.在业务层实现查询用户详情及权限列表

在项目开发时,应该保证“除了Service以外,任何组件不得直接访问持久层”,具体表现为“只有在Service中才可以调用Mapper,其它类中不允许直接调用Mapper”。

所以,在实现Spring Security获取某用户的信息之前,应该先开发以上数据访问的业务层功能,后续,Spring Security将调用业务层对象实现数据访问,不会由Spring Security直接调用持久层!

先在cn.tedu.straw.gateway.service包中创建IUserService接口,并在接口中定义抽象方法:

package cn.tedu.straw.gateway.service;

import cn.tedu.straw.commons.model.User;

public interface IUserService {

    /**
     * 获取用户的详细信息
     *
     * @param username 用户名
     * @return 该用户的详细信息,如果用户名未注册,则返回null
     */
    User getUserInfo(String username);

}

然后,在cn.tedu.straw.gateway.service.impl包中创建UserServiceImpl实现类,实现以上接口,并在类的声明之前添加@Service注解,重写抽象方法:

package cn.tedu.straw.gateway.service.impl;

import cn.tedu.straw.commons.model.User;
import cn.tedu.straw.gateway.mapper.UserMapper;
import cn.tedu.straw.gateway.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User getUserInfo(String username) {
        return userMapper.findByUsername(username);
    }

}

完成后,在testcn.tedu.straw.gateway.service包下创建UserServiceTests测试类,编写并执行单元测试:

package cn.tedu.straw.gateway.service;

import cn.tedu.straw.commons.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceTests {

    @Autowired
    IUserService service;

    @Test
    void getUserInfo() {
        String username = "13800138001";
        User user = service.getUserInfo(username);
        System.err.println("查询结果:" + user);
    }

}

在业务层,除了要查询用户详情以外,还需要查询该用户的权限,所以,在cn.tedu.straw.gateway.service包中创建IPermissionService接口,在接口中定义抽象方法:

package cn.tedu.straw.gateway.service;

import cn.tedu.straw.gateway.vo.PermissionVO;

import java.util.List;

public interface IPermissionService {

    /**
     * 查询某用户的权限列表
     *
     * @param username 用户名
     * @return 该用户的权限列表
     */
    List<PermissionVO> getUserPermissions(String username);

}

然后,在cn.tedu.straw.gateway.service.impl包下创建PermissionServiceImpl类,实现以上IPermissionService接口,在类的声明之前添加@Service注解,并实现以上抽象方法:

package cn.tedu.straw.gateway.service.impl;

import cn.tedu.straw.gateway.mapper.PermissionMapper;
import cn.tedu.straw.gateway.service.IPermissionService;
import cn.tedu.straw.gateway.vo.PermissionVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PermissionServiceImpl implements IPermissionService {

    @Autowired
    PermissionMapper permissionMapper;

    @Override
    public List<PermissionVO> getUserPermissions(String username) {
        return permissionMapper.findByUsername(username);
    }

}

完成后,在testcn.tedu.straw.gateway.service包下创建PermissionServiceTests测试类,编写并执行单元测试:

6.通过用户名获取用户基本信息和权限列表

在Spring Security验证用户登录时,将提供尝试登录的用户名(用户在登录页面输入的用户名),并要求获得UserDetails接口类型的对象,该对象中应该包含用户的基本信息及权限列表,剩下的就由Spring Security完成验证,如果验证通过,Spring Security会将用户的信息存储在Session中,并在后续的访问过程中自动判断用户是否已经登录,及检查相关的授权。

但是,在实际使用时,我们可能需要使用到更多的用户属性,在UserDetails中是没有定义的,例如用户的Id、昵称等数据!所以,应该自定义类进行扩展Spring Security的User类型(该类型是实现 了UserDetails接口的)!

由于登录的用户信息在多个不同的子模块项目中都需要使用到,例如在straw-gateway中,在验证登录时应该返回用户信息,在straw-api-user 或其它子模块项目中,需要获取登录的用户信息来验证用户身份或存储数据等!

所以,先在straw-commonspom.xml中添加依赖Spring Security框架,否则,就不识别User类,就无法扩展:

然后,在straw-commonscn.tedu.straw.commons.security包中自定义LoginUserInfo类,扩展自User类:

package cn.tedu.straw.commons.security;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

@Setter
@Getter
public class LoginUserInfo extends User {

    private Integer id;
    private String nickname;

    public LoginUserInfo(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public LoginUserInfo(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }

    @Override
    public String toString() {
        return "LoginUserInfo >>> " +
                "id=" + id +
                ", nickname=" + nickname +
                ',' + super.toString();
    }
    
}

接下来,在straw-gateway实现UserDetailsService接口,重写接口中的方法,返回UserDetails类型的对象:

package cn.tedu.straw.gateway.security;

import cn.tedu.straw.commons.model.User;
import cn.tedu.straw.commons.security.LoginUserInfo;
import cn.tedu.straw.gateway.service.IPermissionService;
import cn.tedu.straw.gateway.service.IUserService;
import cn.tedu.straw.gateway.vo.PermissionVO;
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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

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

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    IUserService userService;
    @Autowired
    IPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户信息
        User user = userService.getUserInfo(username);
        // 如果没有查询到用户信息,则返回null
        if (user == null) {
            return null;
        }

        // 根据用户名查询该用户的权限信息(列表)
        List<PermissionVO> permissions = permissionService.getUserPermissions(username);

        // 基于以上权限列表创建GrantedAuthority集合
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (PermissionVO permission : permissions) {
            authorities.add(new SimpleGrantedAuthority(permission.getAuthority()));
        }

        // 创建返回对象
//        UserDetails userDetails = org.springframework.security.core.userdetails.User.builder()
//                .username(user.getUsername())
//                .password(user.getPassword())
//                .disabled(user.getIsEnabled() == 0)
//                .accountLocked(user.getIsLocked() == 1)
//                .accountExpired(false)
//                .credentialsExpired(false)
//                .authorities(authorities)
//                .build();
        // 关于构造方法的参数,在构造方法的括号中按下Ctrl+P可以提示参数列表
        // String username:用户名
        // String password:密码
        // boolean enabled:账号是否启用
        // boolean accountNonExpired:账号是否未过期
        // boolean credentialsNonExpired:证书是否未过期
        // boolean accountNonLocked:账号未被锁定
        // Collection<? extends GrantedAuthority> authorities:权限列表
        LoginUserInfo loginUserInfo = new LoginUserInfo(
                user.getUsername(),
                user.getPassword(),
                user.getIsEnabled() == 1,
                true,
                true,
                user.getIsLocked() == 0,
                authorities
        );
        loginUserInfo.setId(user.getId());
        loginUserInfo.setNickname(user.getNickname());

        // 返回用户信息
        return loginUserInfo;
    }

}

完成后,在testcn.tedu.straw.gateway.security包中创建UserDetailsTests测试类,测试获取用户信息:

package cn.tedu.straw.gateway.security;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.userdetails.UserDetails;

@SpringBootTest
public class UserDetailsTests {

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Test
    void loadUserByUsername() {
        String username = "13800138001";
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        System.err.println("根据用户名" + username + "查询到的用户信息:");
        System.err.println(">>> " + userDetails);
    }

}

当提供的用户名是正确的时,测试结果例如:

根据用户名13800138001查询到的用户信息:
>>> LoginUserInfo >>> 
	id=1, 
	nickname=WKJ,
	cn.tedu.straw.commons.security.LoginUserInfo@5549e271: 
		Username: 13800138001; 
		Password: [PROTECTED]; 
		Enabled: true; 
		AccountNonExpired: true; 
		credentialsNonExpired: true; 
		AccountNonLocked: true; 
		Granted Authorities: 
			/answer/post,
			/comment/post,
			/question/detail,
			/question/post,
			/question/upload,
			/web/index

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值