SpringSecurity在微服务中的应用

前述

SpringCloud几个依赖关系

依赖版本号
spring-boot2.7.2
spring-cloud2021.0.3
spring-cloud-alibaba2021.0.1.0
nacos-client2.1.0
openfeign3.1.3

目录结构

SpringSecurity

父POM

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>security-cloud</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <name>security-cloud</name>
  <description>security-cloud:security微服务下的使用</description>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.7.2</spring-boot.version>
    <spring-cloud.version>2021.0.3</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    <alibaba.nacos.version>2.1.0</alibaba.nacos.version>
    <cloud-openfeign.version>3.1.3</cloud-openfeign.version>
    <mybatis-plus-boot-starter.version>3.5.1</mybatis-plus-boot-starter.version>
    <druid-spring-boot-starter.version>1.2.8</druid-spring-boot-starter.version>
    <jwt.version>0.9.1</jwt.version>
    <hutool-all.version>5.8.4</hutool-all.version>
    <commons-lang3.version>3.12.0</commons-lang3.version>
    <fastjson2.version>2.0.9</fastjson2.version>
  </properties>

  <dependencyManagement>
    <dependencies>

      <!-- SpringBoot 依赖配置 -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- SpringCloud 微服务 -->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!--spring-cloud-alibaba-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>${spring-cloud-alibaba.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Alibaba Nacos 配置 -->
      <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>${alibaba.nacos.version}</version>
      </dependency>

      <!-- openfeign -->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <version>${cloud-openfeign.version}</version>
      </dependency>

      <!--mybatis-plus 持久层-->
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus-boot-starter.version}</version>
      </dependency>

      <!-- JWT -->
      <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>${jwt.version}</version>
      </dependency>

      <!--JSON解析-->
      <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>${fastjson2.version}</version>
      </dependency>

      <!--java工具类-->
      <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>${hutool-all.version}</version>
      </dependency>

      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>${commons-lang3.version}</version>
      </dependency>

    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- bootstrap 启动器:解决Cloud高版本下不先加载bootstrap.properties问题
        see github issue:https://github.com/alibaba/spring-cloud-alibaba/issues/1994
     -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
  </dependencies>

  <modules>
    <module>security-common</module>
    <module>security-modules</module>
    <module>security-gateway</module>
  </modules>
  <packaging>pom</packaging>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

security-common-security模块

SpringSecurity

退出处理器

/**
 * @version 1.0.0
 * @className: LogoutSuccessHandlerImpl
 * @description: 退出处理器
 * @author: LiJunYi
 * @create: 2022/7/26 8:34
 */
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Resource
    private TokenManager tokenManager;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication e) throws IOException {
        LoginUser loginUser = tokenManager.getLoginUser(request);
        if(ObjectUtil.isNotNull(loginUser))
        {
            //移除
            tokenManager.removeToken(loginUser.getToken());
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
    }
}

security-gateway网关模块

依赖引入

注意事项: spring-cloud-starter-alibaba-nacos-config 模块移除了 spring-cloud-starter-bootstrap 依赖,如果你想以旧版的方式使用,你需要手动加上该依赖

详细见:Spring Cloud Alibaba 2021.0.1.0 升级指南open in new window

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>security-cloud</artifactId>
        <groupId>org.example</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>security-gateway</artifactId>

    <dependencies>
        <!--引入公共模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>security-common-base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

bootstrap.yml

server:
  port: 9527
spring:
  application:
    name: security-gateway
  profiles:
    active: dev
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: security-system
          uri: lb://security-system
          predicates:
            - Path=/sys/**

security-system业务模块

依赖引入

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>security-modules</artifactId>
        <groupId>org.example</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>security-system</artifactId>

    <dependencies>
        <!--引入公共模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>security-common-base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--引入security模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>security-common-security</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--服务调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- SpringBoot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--JSON解析-->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>

        <!--java工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 8009
spring:
  application:
    name: security-system
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  redis:
    host: localhost
    port: 6379
    database: 0
    password:
    timeout: 10s
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/security-simple?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: admin
    driver-class-name: com.mysql.cj.jdbc.Driver
#mybatis-plus
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
management:
  endpoints:
    web:
      exposure:
        exclude: '*'

MyUserDetailsServiceImpl

/**
 * @version 1.0.0
 * @className: MyUserDetailsServiceImpl
 * @description: security具体登录逻辑
 * @author: LiJunYi
 * @create: 2022/7/26 9:11
 */
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService
{
    private static final Logger log = LoggerFactory.getLogger(MyUserDetailsServiceImpl.class);

    private final ISysUserService userService;

    private final SysPermissionService permissionService;

    /**
     * 构造器注入
     *
     * @param userService       用户服务
     * @param permissionService 许可服务
     */
    public MyUserDetailsServiceImpl(ISysUserService userService, SysPermissionService permissionService)
    {
        this.userService = userService;
        this.permissionService = permissionService;
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        SysUser user = userService.getByUsername(username);
        if (ObjectUtil.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        }
        // 获取角色
        List<SysRole> roles = permissionService.getRolesByUserId(user);
        user.setRoles(roles);
        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(Convert.toLong(user.getId()), 1000L, user, permissionService.getMenuPermission(user));
    }

}

登录处理类

/**
 * @version 1.0.0
 * @className: SysLoginService
 * @description: 登录业务
 * @author: LiJunYi
 * @create: 2022/7/27 12:53
 */
@Component
public class SysLoginService
{
    @Autowired
    private TokenManager tokenManager;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @return 结果
     */
    public String login(String username, String password)
    {
        // 用户验证
        Authentication authentication;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                throw new ServiceException("用户名密码错误");
            }
            else
            {
                throw new ServiceException(e.getMessage());
            }
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token
        return tokenManager.createToken(loginUser);
    }
}

启动服务

1、分别启动我们的Redis以及Nacos

2、启动业务模块以及网关模块

3、访问:http://localhost:8848/nacos/,观察Nacos控制台服务列表

SpringSecurity

测试效果图

SpringSecuritySpringSecuritySpringSecurity

最后

目前这个示例只是 SpringSecurity 在微服务中的简单使用,而且鉴权等也没有放在网关上进行,而是放在了业务模块上。

后续会进一步修改,将鉴权放在网关上。然后就是会加入 Spring Security Oauth2 来实现服务间的鉴权。

如果示例中有什么不对的地方,希望大家多多指正,有修改才有进步!

Last Updated 6/14/2024, 3:05:31 AM