feat:权限v1

This commit is contained in:
xiang
2025-08-31 00:21:22 +08:00
parent a64923a794
commit f0582a11ab
10 changed files with 98 additions and 8 deletions

View File

@@ -1,4 +1,19 @@
package com.xiang.xservice.auth.api.api; package com.xiang.xservice.auth.api.api;
import com.xiang.xservice.auth.api.dto.req.LoginRequest;
import com.xiang.xservice.auth.api.dto.req.RegisterRequest;
import com.xiang.xservice.auth.api.dto.resp.LoginResp;
import com.xiang.xservice.auth.api.dto.resp.RegisterResp;
import com.xiang.xservice.basic.common.resp.Result;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public interface TokenApi { public interface TokenApi {
Result<LoginResp> login(@RequestBody @NotNull(message = "请求参数不能为空") @Valid LoginRequest request);
Result<RegisterResp> register(@RequestBody @Valid @NotNull(message = "请求参数不能为空") RegisterRequest request);
} }

View File

@@ -13,6 +13,7 @@ import lombok.Getter;
public enum Code02RoleErrorCode implements BaseErrorCode { public enum Code02RoleErrorCode implements BaseErrorCode {
ROLE_NOT_EXISTS("A1000201", "角色不存在"), ROLE_NOT_EXISTS("A1000201", "角色不存在"),
USER_ROLE_NOT_EXISTS("A1000202", "用户角色权限不存在!")
; ;
private final String code; private final String code;

View File

@@ -5,12 +5,23 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class LoginRequest { public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username; private String username;
@NotBlank(message = "密码不能为空")
private String password; private String password;
/**
* todo 目前只支持用户名密码登录
* 1. 用户名密码登录
* 2. 手机号密码登录
* 3. 邮箱密码登录
*/
private Integer loginType;
} }

View File

@@ -1,5 +1,6 @@
package com.xiang.xservice.auth.server.controller; package com.xiang.xservice.auth.server.controller;
import com.xiang.xservice.auth.api.api.TokenApi;
import com.xiang.xservice.auth.api.dto.req.LoginRequest; import com.xiang.xservice.auth.api.dto.req.LoginRequest;
import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest;
import com.xiang.xservice.auth.api.dto.resp.LoginResp; import com.xiang.xservice.auth.api.dto.resp.LoginResp;
@@ -20,7 +21,7 @@ import java.util.Objects;
@Slf4j @Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
public class TokenController { public class TokenController implements TokenApi {
private final XUserService userService; private final XUserService userService;

View File

@@ -12,6 +12,7 @@ import com.xiang.xservice.basic.common.resp.Result;
import com.xiang.xservice.basic.exception.BusinessException; import com.xiang.xservice.basic.exception.BusinessException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -31,7 +32,6 @@ public class UserController {
private final XUserService userService; private final XUserService userService;
@PostMapping("/private/user/list") @PostMapping("/private/user/list")
public Result<UserResp> getUserList(@RequestBody @Valid @NotNull(message = "请求参数不能为空") UserQueryRequest request) { public Result<UserResp> getUserList(@RequestBody @Valid @NotNull(message = "请求参数不能为空") UserQueryRequest request) {
return Result.success(userService.getUserList(request)); return Result.success(userService.getUserList(request));

View File

@@ -4,6 +4,8 @@ import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SecurityContext;
import com.xiang.xservice.basic.exception.CustomAccessDeniedHandler;
import com.xiang.xservice.basic.exception.CustomAuthenticationEntryPoint;
import com.xiang.xservice.basic.utils.JwkUtils; import com.xiang.xservice.basic.utils.JwkUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -21,6 +23,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
@@ -45,6 +48,8 @@ public class AuthorizationServerConfig {
@Value("${user.auth.redirectUrl}") @Value("${user.auth.redirectUrl}")
private String redirectUrl; private String redirectUrl;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
@Bean @Bean
@Order(1) @Order(1)
@@ -62,9 +67,20 @@ public class AuthorizationServerConfig {
.authorizeRequests(authorizeRequests -> authorizeRequests .authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/public/**").permitAll() .antMatchers("/public/**").permitAll()
.antMatchers("/open/**").permitAll() .antMatchers("/open/**").permitAll()
.antMatchers("/private/**").authenticated()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); .exceptionHandling(exception ->
exception
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth ->
oauth
.jwt()
.and()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler));
return http.build(); return http.build();
} }
@@ -131,5 +147,10 @@ public class AuthorizationServerConfig {
.issuer(issuer) .issuer(issuer)
.build(); .build();
} }
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
} }

View File

@@ -18,4 +18,6 @@ public interface XUserRoleMapper {
int delByRoleIds(@Param("list") List<Long> roleIds); int delByRoleIds(@Param("list") List<Long> roleIds);
int addBatch(List<XUserRole> list); int addBatch(List<XUserRole> list);
List<XUserRole> getByUserId(@Param("userId") Long userId);
} }

View File

@@ -1,13 +1,21 @@
package com.xiang.xservice.auth.service.service; package com.xiang.xservice.auth.service.service;
import com.google.common.collect.Lists;
import com.xiang.xservice.auth.service.entity.XRole;
import com.xiang.xservice.auth.service.entity.XUser; import com.xiang.xservice.auth.service.entity.XUser;
import com.xiang.xservice.auth.service.entity.XUserRole;
import com.xiang.xservice.auth.service.repository.mapper.XRoleMapper;
import com.xiang.xservice.auth.service.repository.mapper.XUserMapper; import com.xiang.xservice.auth.service.repository.mapper.XUserMapper;
import com.xiang.xservice.auth.service.repository.mapper.XUserRoleMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects; import java.util.Objects;
@Service @Service
@@ -15,6 +23,8 @@ import java.util.Objects;
public class CustomUserDetailsService implements UserDetailsService { public class CustomUserDetailsService implements UserDetailsService {
private final XUserMapper userMapper; private final XUserMapper userMapper;
private final XRoleMapper roleMapper;
private final XUserRoleMapper userRoleMapper;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
@@ -22,11 +32,17 @@ public class CustomUserDetailsService implements UserDetailsService {
if (Objects.isNull(user)) { if (Objects.isNull(user)) {
throw new RuntimeException("用户不存在!"); throw new RuntimeException("用户不存在!");
} }
List<XUserRole> userRoles = userRoleMapper.getByUserId(user.getId());
List<Long> roleIds = userRoles.stream().map(XUserRole::getRoleId).toList();
List<SimpleGrantedAuthority> grantedAuthorities = Lists.newArrayList();
if (CollectionUtils.isNotEmpty(roleIds)) {
List<XRole> roles = roleMapper.getRoleByIds(roleIds);
grantedAuthorities.addAll(roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getCode())).toList());
}
return org.springframework.security.core.userdetails.User return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername()) .withUsername(user.getUsername())
.password(user.getPassword()) // 已经加密的 .password(user.getPassword()) // 已经加密的
.authorities("admin") .authorities(grantedAuthorities)
.build(); .build();
} }
} }

View File

@@ -2,6 +2,7 @@ package com.xiang.xservice.auth.service.service.impl;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.xiang.xservice.auth.api.code.Code01UserErrorCode; import com.xiang.xservice.auth.api.code.Code01UserErrorCode;
import com.xiang.xservice.auth.api.code.Code02RoleErrorCode;
import com.xiang.xservice.auth.api.dto.req.LoginRequest; import com.xiang.xservice.auth.api.dto.req.LoginRequest;
import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest;
@@ -13,6 +14,7 @@ import com.xiang.xservice.auth.api.dto.resp.LoginResp;
import com.xiang.xservice.auth.api.dto.resp.RegisterResp; import com.xiang.xservice.auth.api.dto.resp.RegisterResp;
import com.xiang.xservice.auth.api.dto.resp.UserResp; import com.xiang.xservice.auth.api.dto.resp.UserResp;
import com.xiang.xservice.auth.service.convert.XUserConvert; import com.xiang.xservice.auth.service.convert.XUserConvert;
import com.xiang.xservice.auth.service.entity.XRole;
import com.xiang.xservice.auth.service.entity.XUser; import com.xiang.xservice.auth.service.entity.XUser;
import com.xiang.xservice.auth.service.entity.XUserRole; import com.xiang.xservice.auth.service.entity.XUserRole;
import com.xiang.xservice.auth.service.enums.UserStatusEnum; import com.xiang.xservice.auth.service.enums.UserStatusEnum;
@@ -22,6 +24,7 @@ import com.xiang.xservice.auth.service.repository.mapper.XUserRoleMapper;
import com.xiang.xservice.auth.service.service.XUserService; import com.xiang.xservice.auth.service.service.XUserService;
import com.xiang.xservice.basic.enums.DelStatusEnum; import com.xiang.xservice.basic.enums.DelStatusEnum;
import com.xiang.xservice.basic.exception.BusinessException; import com.xiang.xservice.basic.exception.BusinessException;
import com.xiang.xservice.basic.utils.JsonUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
@@ -41,6 +44,7 @@ import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@@ -67,16 +71,30 @@ public class XUserServiceImpl implements XUserService {
throw new BusinessException(e.getMessage()); throw new BusinessException(e.getMessage());
} }
XUser user = userMapper.selectByUsername(request.getUsername());
if (Objects.isNull(user)) {
throw new BusinessException(Code01UserErrorCode.USER_NOT_EXISTS);
}
List<String> roleCodes = Lists.newArrayList();
List<XUserRole> userRoles = userRoleMapper.getByUserId(user.getId());
if (CollectionUtils.isNotEmpty(userRoles)) {
List<XRole> roles = roleMapper.getRoleByIds(userRoles.stream().map(XUserRole::getRoleId).collect(Collectors.toList()));
if (CollectionUtils.isEmpty(roles)) {
throw new BusinessException(Code02RoleErrorCode.ROLE_NOT_EXISTS);
}
roleCodes.addAll(roles.stream().map(XRole::getCode).toList());
}
// 生成 token // 生成 token
Instant now = Instant.now(); Instant now = Instant.now();
// todo 1. 构建 claims
JwtClaimsSet claims = JwtClaimsSet.builder() JwtClaimsSet claims = JwtClaimsSet.builder()
// 对应 ProviderSettings.issuer // 对应 ProviderSettings.issuer
.issuedAt(now) .issuedAt(now)
.expiresAt(now.plus(1, ChronoUnit.HOURS)) .expiresAt(now.plus(3, ChronoUnit.HOURS))
// 自定义 scope // 自定义 scope
.claim("timestamp", System.currentTimeMillis())
.claim("username", request.getUsername()) .claim("username", request.getUsername())
.claim("roles", JsonUtils.toJsonString(roleCodes))
.build(); .build();
// 2. 编码生成 token // 2. 编码生成 token

View File

@@ -38,5 +38,10 @@
</foreach> </foreach>
</insert> </insert>
<select id="getByUserId" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/>
from x_user_role
where user_id = #{userId}
</select>
</mapper> </mapper>