引言
这篇通过集成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最新面试题一套和项目源码