前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud 实战|整合admin模块-优化认证中心很难么?

Spring Cloud 实战|整合admin模块-优化认证中心很难么?

作者头像
AI码师
发布2022-09-19 11:47:06
4880
发布2022-09-19 11:47:06
举报

引言

这篇通过集成admin模块,实现用户,角色和权限相关接口,全部从数据库中获取,并且重构auth模块,auth模块通过feign 调用admin服务,获取用户信息和客户端信息。

开整

创建admin 父工程

在ams-cloud 下添加新的子模块 ams-admin

在这里插入图片描述

在这里插入图片描述

因为ams-admin是一个父工程,所以需要移除src模块并在pom中添加pom

创建子模块 admin-api

在admin下创建子模块 admin-api

在这里插入图片描述

调整pom文件,修复父子关系

在这里插入图片描述

在这里插入图片描述

引入依赖

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>com.ams</groupId>
        <artifactId>common-base</artifactId>
        <version>${ams.version}</version>
    </dependency>

    <dependency>
        <groupId>com.ams</groupId>
        <artifactId>common-web</artifactId>
        <version>${ams.version}</version>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- openfeign依赖 1. http客户端选择okhttp 2. loadbalancer替换ribbon -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>

</dependencies>

创建dto

添加客户端dto
代码语言:javascript
复制
package com.ams.admin.dto;

import lombok.Data;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Data
public class OAuth2ClientDTO {

    /**
     * 客户端ID
     */
    private String clientId;

    /**
     * 客户端密钥
     */
    private String clientSecret;

    /**
     * 资源id列表
     */
    private String resourceIds;

    /**
     * 授权范围
     */
    private String scope;

    /**
     * 授权方式
     */
    private String authorizedGrantTypes;

    /**
     * 回调地址
     */
    private String webServerRedirectUri;

    /**
     * 权限列表
     */
    private String authorities;

    /**
     * 认证令牌时效
     */
    private Integer accessTokenValidity;

    /**
     * 刷新令牌时效
     */
    private Integer refreshTokenValidity;

    /**
     * 扩展信息
     */
    private String additionalInformation;

    /**
     * 是否自动放行
     */
    private String autoapprove;

}

添加角色权限dto
代码语言:javascript
复制
package com.ams.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.List;
/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Data
@Accessors(chain = true)
public class RolePermissionDTO {
    private Long roleId;
    private List<Long> permissionIds;
    private Long menuId;
}

用户信息dto
代码语言:javascript
复制
package com.ams.admin.dto;

import lombok.Data;

import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Data
public class UserAuthDTO {

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

    /**
     * 用户状态:1-有效;0-禁用
     */
    private Integer status;

    /**
     * 用户角色编码集合 ["ROOT","ADMIN"]
     */

    private List<String> roles;



}

创建feign接口

创建获取客户端信息的feign
代码语言:javascript
复制
package com.ams.admin.api;

import com.ams.admin.dto.OAuth2ClientDTO;
import com.ams.common.result.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@FeignClient(value = "ams-admin", contextId = "oauth-client")
public interface OAuthClientFeignClient {

    @GetMapping("/api/oauth-clients/getOAuth2ClientById")
    R<OAuth2ClientDTO> getOAuth2ClientById(@RequestParam String clientId);
}

创建根据用户名获取用户信息的feign
代码语言:javascript
复制
package com.ams.admin.api;

import com.ams.admin.dto.UserAuthDTO;
import com.ams.common.result.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@FeignClient(value = "ams-admin")
public interface UserFeignClient {

    @GetMapping("/api/v1/users/username/{username}")
    R<UserAuthDTO> getUserByUsername(@PathVariable String username);
}

创建子模块admin-boot

创建方式和创建admin-api一样,就不贴步骤了,这里直接写核心步骤,需要代码的可以私信我

引入依赖

代码语言:javascript
复制
 <dependencies>
        <!-- 配置读取 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring Cloud & Alibaba -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <!-- 注册中心 -->
        <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-loadbalancer</artifactId>
        </dependency>

        <!-- 配置中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- JWT库 -->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
        </dependency>

        <dependency>
            <groupId>com.ams</groupId>
            <artifactId>admin-api</artifactId>
            <version>${ams.version}</version>
        </dependency>
        <dependency>
            <groupId>com.ams</groupId>
            <artifactId>common-redis</artifactId>
            <version>${ams.version}</version>
        </dependency>
        <dependency>
            <groupId>com.ams</groupId>
            <artifactId>common-mybatis-plus</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

添加启动监听器,初始化角色权限

代码语言:javascript
复制
package com.ams.admin.component.listener;

import com.ams.admin.service.ISysPermissionService;
import lombok.AllArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Component
@AllArgsConstructor
public class InitResourcePermissionCache implements CommandLineRunner {

    private ISysPermissionService iSysPermissionService;

    @Override
    public void run(String... args) {
        iSysPermissionService.refreshPermRolesRules();
    }
}

代码语言:javascript
复制
package com.ams.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.ams.admin.mapper.SysPermissionMapper;
import com.ams.admin.pojo.entity.SysPermission;
import com.ams.admin.service.ISysPermissionService;
import com.ams.common.constant.GlobalConstants;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Service
@RequiredArgsConstructor
public class SysPermissionServiceImpl extends ServiceImpl<SysPermissionMapper, SysPermission> implements ISysPermissionService {

    private final RedisTemplate redisTemplate;

    @Override
    public boolean refreshPermRolesRules() {
        redisTemplate.delete(Arrays.asList(GlobalConstants.URL_PERM_ROLES_KEY));
        List<SysPermission> permissions = this.listPermRoles();
        if (CollectionUtil.isNotEmpty(permissions)) {
            List<SysPermission> urlPermList = permissions.stream()
                    .filter(item -> StrUtil.isNotBlank(item.getUrlPerm()))
                    .collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(urlPermList)) {
                Map<String, List<String>> urlPermRoles = new HashMap<>();
                urlPermList.stream().forEach(item -> {
                    String perm = item.getUrlPerm();
                    List<String> roles = item.getRoles();
                    urlPermRoles.put(perm, roles);
                });
                redisTemplate.opsForHash().putAll(GlobalConstants.URL_PERM_ROLES_KEY, urlPermRoles);
            }
        }
        return true;
    }

    @Override
    public List<SysPermission> listPermRoles() {
        return this.baseMapper.listPermRoles();
    }
}

实现我们刚才定义的两个feign接口

代码语言:javascript
复制
@RequestMapping("/api/oauth-clients")
@Slf4j
@AllArgsConstructor
@RestController
public class OauthClientController {
    private ISysOauthClientService iSysOauthClientService;

    @GetMapping("/getOAuth2ClientById")
    public R<OAuth2ClientDTO> getOAuth2ClientById(@RequestParam String clientId) {
        SysOauthClient client = iSysOauthClientService.getById(clientId);
        Assert.notNull(client, "OAuth2 客户端不存在");
        OAuth2ClientDTO oAuth2ClientDTO = new OAuth2ClientDTO();
        BeanUtil.copyProperties(client, oAuth2ClientDTO);
        return R.ok(oAuth2ClientDTO);
    }
}
代码语言:javascript
复制
package com.ams.admin.controller;

import com.ams.admin.dto.UserAuthDTO;
import com.ams.admin.service.ISysUserService;
import com.ams.common.result.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@RestController
@RequestMapping("/api/v1/users")
@Slf4j
@RequiredArgsConstructor
public class UserController {

    private final ISysUserService iSysUserService;

    /**
     * 获取用户信息
     */
    @GetMapping("/username/{username}")
    public R<UserAuthDTO> getUserByUsername(@PathVariable String username) {
        UserAuthDTO user = iSysUserService.getByUsername(username);
        return R.ok(user);
    }
}

创建bootstrap.yml配置文件

代码语言:javascript
复制
server:
  port: 2004

spring:
  application:
    name: ams-admin
  cloud:
    nacos:
      # 注册中心
      discovery:
        server-addr: http://xxx.xxx.xxx:8848
      # 配置中心
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yaml
        shared-configs[0]:
          data-id: ams-common.yaml
          refresh: true
logging:
  level:
    spring.: DEBUG

创建nacos配置文件

在nacos中新增ams-admin配置

代码语言:javascript
复制
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver 
    url: jdbc:mysql://${mysql.host}:${mysql.port}/ams_admin?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: ${mysql.username}
    password: ${mysql.password}
  redis:
    database: 0
    host: ${redis.host}
    port: ${redis.port}
    password: ${redis.password}
  cache:
    # 缓存类型
    type: redis
    # 缓存时间(单位:ms)
    redis:
      time-to-live: 3600000
      # 缓存null值,防止缓存穿透
      cache-null-values: true
      # 允许使用缓存前缀,
      use-key-prefix: true
      # 缓存前缀,没有设置使用注解的缓存名称(value)作为前缀,和注解的key用双冒号::拼接组成完整缓存key
      key-prefix: 'admin:'
                
mybatis-plus:
  configuration:
    # 驼峰下划线转换
    map-underscore-to-camel-case: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 全局参数设置
ribbon:
  ReadTimeout: 120000
  ConnectTimeout: 10000
  SocketTimeout: 10000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1 

feign:
  httpclient:
    enabled: true
  okhttp:
    enabled: false


创建启动类

代码语言:javascript
复制
package com.ams.admin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/30
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@SpringBootApplication
@EnableDiscoveryClient
@RefreshScope
public class AdminApp {
    public static void main(String[] args) {
        SpringApplication.run(AdminApp.class, args);
    }
}

调整ams-auth模块

调整后的ams-auth模块是通过feign去调用admin服务,获取认证需要的信息。

引入admin-api依赖

代码语言:javascript
复制

        <dependency>
            <groupId>com.ams</groupId>
            <artifactId>admin-api</artifactId>
            <version>1.0.0</version>
        </dependency>

修改SysUserDetailsServiceImpl 用户获取方式

代码语言:javascript
复制
package com.ams.auth.security.details.user;

import com.ams.admin.api.UserFeignClient;
import com.ams.admin.dto.UserAuthDTO;
import com.ams.auth.comm.enums.PasswordEncoderTypeEnum;
import com.ams.common.result.R;
import com.ams.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

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

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Service("sysUserDetailsService")
@Slf4j
@RequiredArgsConstructor
public class SysUserDetailsServiceImpl implements UserDetailsService {
    private final UserFeignClient userFeignClient;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 后面从管理端获取用户信息
        R<UserAuthDTO> result = userFeignClient.getUserByUsername(username);
        SysUserDetails userDetails = null;
        if (R.ok().getCode().equals(result.getCode())) {
            UserAuthDTO user = result.getData();
            if (null != user) {
                userDetails = SysUserDetails.builder()
                        .userId(user.getUserId())
                        .username(user.getUsername())
                        .authorities(handleRoles(user.getRoles()))
                        .enabled(user.getStatus() == 1)
                        .password(PasswordEncoderTypeEnum.BCRYPT.getPrefix() + user.getPassword())
                        .build();
            }
        }
        if (Objects.isNull(userDetails)) {
            throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
        } else if (!userDetails.isEnabled()) {
            throw new DisabledException("该账户已被禁用!");
        } else if (!userDetails.isAccountNonLocked()) {
            throw new LockedException("该账号已被锁定!");
        } else if (!userDetails.isAccountNonExpired()) {
            throw new AccountExpiredException("该账号已过期!");
        }
        return userDetails;
    }

    private Collection<SimpleGrantedAuthority> handleRoles(List<String> roles) {
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }

    private SysUserDetails loadUser(String username) {
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("admin"));
        authorities.add(new SimpleGrantedAuthority("root"));
        return SysUserDetails.builder()
                .userId(1L)
                .username(username)
                .enabled(true)
                .authorities(authorities)
                .password(PasswordEncoderTypeEnum.BCRYPT.getPrefix() + new BCryptPasswordEncoder().encode("123456789")).build();
    }

}

修改ClientDetailsServiceImpl 客户端信息获取方式

代码语言:javascript
复制
package com.ams.auth.security.details.client;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.ams.admin.api.OAuthClientFeignClient;
import com.ams.admin.dto.OAuth2ClientDTO;
import com.ams.auth.comm.enums.PasswordEncoderTypeEnum;
import com.ams.common.result.R;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@Service
@RequiredArgsConstructor
public class ClientDetailsServiceImpl implements ClientDetailsService {
    private final OAuthClientFeignClient oAuthClientFeignClient;

    @Override
    public ClientDetails loadClientByClientId(String clientId) {
        // 通过feign 调用admin服务获取client信息
        R<OAuth2ClientDTO> result = oAuthClientFeignClient.getOAuth2ClientById(clientId);
        if (R.ok().getCode().equals(result.getCode())) {
            OAuth2ClientDTO client = result.getData();
            BaseClientDetails clientDetails = new BaseClientDetails(
                    client.getClientId(),
                    client.getResourceIds(),
                    client.getScope(),
                    client.getAuthorizedGrantTypes(),
                    client.getAuthorities(),
                    client.getWebServerRedirectUri());
            clientDetails.setClientSecret(PasswordEncoderTypeEnum.NOOP.getPrefix() + client.getClientSecret());
            clientDetails.setAccessTokenValiditySeconds(client.getAccessTokenValidity());
            clientDetails.setRefreshTokenValiditySeconds(client.getRefreshTokenValidity());
            return clientDetails;
        } else {
            throw new NoSuchClientException(result.getMsg());
        }
    }
}

修改启动类,启用feign

代码语言:javascript
复制
package com.ams.auth;

import com.ams.admin.api.OAuthClientFeignClient;
import com.ams.admin.api.UserFeignClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: AI码师 关注公众号"AI码师"获取完整源码
 * @date: 2021/11/24
 * @description:
 * @modifiedBy:
 * @version: 1.0
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackageClasses = {UserFeignClient.class, OAuthClientFeignClient.class})
public class AuthApp {
    public static void main(String[] args) {
        SpringApplication.run(AuthApp.class, args);
    }
}

到这里,已经全部调整完成,自行验证下token获取是否生效

关注公众号领取2021最新面试题一套和项目源码

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 乐哥聊编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 开整
    • 创建admin 父工程
      • 创建子模块 admin-api
        • 调整pom文件,修复父子关系
        • 引入依赖
        • 创建dto
        • 创建feign接口
      • 创建子模块admin-boot
        • 引入依赖
        • 添加启动监听器,初始化角色权限
        • 实现我们刚才定义的两个feign接口
        • 创建bootstrap.yml配置文件
        • 创建nacos配置文件
        • 创建启动类
      • 调整ams-auth模块
        • 引入admin-api依赖
        • 修改SysUserDetailsServiceImpl 用户获取方式
        • 修改ClientDetailsServiceImpl 客户端信息获取方式
        • 修改启动类,启用feign
    相关产品与服务
    对象存储
    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档