feat:自定义接口权限
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package com.xiang.xservice.auth.service.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AuthorizationBeanConfig {
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
// 默认从 "scope" 取,这里改成你自定义的 "authorities"
|
||||
authoritiesConverter.setAuthoritiesClaimName("authorities");
|
||||
authoritiesConverter.setAuthorityPrefix(""); // 不要加 "SCOPE_"
|
||||
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
|
||||
return jwtAuthenticationConverter;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
@@ -33,7 +34,9 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
@@ -50,6 +53,7 @@ public class AuthorizationServerConfig {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
|
||||
private final CustomAccessDeniedHandler customAccessDeniedHandler;
|
||||
private final JwtAuthenticationConverter jwtAuthenticationConverter;
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
@@ -61,7 +65,9 @@ public class AuthorizationServerConfig {
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http,
|
||||
CustomSecurityMetadataSource metadataSource,
|
||||
CustomAccessDecisionManager decisionManager) throws Exception {
|
||||
http
|
||||
.csrf().disable() // 禁用 CSRF
|
||||
.authorizeRequests(authorizeRequests -> authorizeRequests
|
||||
@@ -69,18 +75,23 @@ public class AuthorizationServerConfig {
|
||||
.antMatchers("/open/**").permitAll()
|
||||
.antMatchers("/private/**").authenticated()
|
||||
.anyRequest().authenticated()
|
||||
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
|
||||
@Override
|
||||
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
|
||||
object.setSecurityMetadataSource(metadataSource);
|
||||
object.setAccessDecisionManager(decisionManager);
|
||||
return object;
|
||||
}
|
||||
})
|
||||
)
|
||||
.exceptionHandling(exception ->
|
||||
exception
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint)
|
||||
.accessDeniedHandler(customAccessDeniedHandler))
|
||||
.exceptionHandling(exception -> exception
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint)
|
||||
.accessDeniedHandler(customAccessDeniedHandler))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.oauth2ResourceServer(oauth ->
|
||||
oauth
|
||||
.jwt()
|
||||
.and()
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint)
|
||||
.accessDeniedHandler(customAccessDeniedHandler));
|
||||
.oauth2ResourceServer(oauth2 ->
|
||||
oauth2
|
||||
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.xiang.xservice.auth.service.config;
|
||||
|
||||
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 访问决策器
|
||||
* 决定用户是否有权访问
|
||||
*/
|
||||
@Component
|
||||
public class CustomAccessDecisionManager implements AccessDecisionManager {
|
||||
@Override
|
||||
public void decide(Authentication authentication, Object object,
|
||||
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
|
||||
|
||||
if (configAttributes == null || configAttributes.isEmpty()) {
|
||||
return; // 没有限制,直接放行
|
||||
}
|
||||
|
||||
Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
|
||||
|
||||
for (ConfigAttribute attribute : configAttributes) {
|
||||
String requiredRole = attribute.getAttribute();
|
||||
for (GrantedAuthority authority : userAuthorities) {
|
||||
if (requiredRole.trim().equals(authority.getAuthority())) {
|
||||
return; // 匹配成功
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new AccessDeniedException("用户没有权限!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.xiang.xservice.auth.service.config;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.xiang.xservice.auth.api.dto.resp.PermissionRoleDTO;
|
||||
import com.xiang.xservice.auth.service.repository.mapper.XPermissionMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 动态权限数据源 (核心)
|
||||
* 当前请求需要哪些角色。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
||||
private final XPermissionMapper permissionMapper;
|
||||
private final Map<String, Collection<ConfigAttribute>> permissionMap = Maps.newHashMap();
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void loadPermissionMap() {
|
||||
Map<String, Collection<ConfigAttribute>> map = loadPermission();
|
||||
permissionMap.putAll(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
|
||||
HttpServletRequest request = ((FilterInvocation) object).getRequest();
|
||||
String requestUrl = request.getRequestURI();
|
||||
String method = request.getMethod();
|
||||
String key = requestUrl + ":" + method;
|
||||
return permissionMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
||||
return permissionMap.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FilterInvocation.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库加载所有 URL → 对应角色 的映射
|
||||
* @return Map<String, Collection<ConfigAttribute>>
|
||||
*/
|
||||
private Map<String, Collection<ConfigAttribute>> loadPermission() {
|
||||
Map<String, Collection<ConfigAttribute>> map = new HashMap<>();
|
||||
List<PermissionRoleDTO> list = permissionMapper.loadAllPermission();
|
||||
for (PermissionRoleDTO dto : list) {
|
||||
String key = dto.getApiUrl() + ":" + dto.getMethod();
|
||||
map.computeIfAbsent(key, k -> new ArrayList<>())
|
||||
.add(new SecurityConfig(dto.getRoleCode()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.xiang.xservice.auth.service.constants;
|
||||
|
||||
public class RedisConstant {
|
||||
|
||||
public static final String XS_PERMISSION_ROLE = "auth:permission:role";
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.xiang.xservice.auth.service.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class XPermission {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 类型 1=菜单 2=按钮 3=接口
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 父类id
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 路径接口
|
||||
*/
|
||||
private String apiPath;
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 删除标识(0:未删除 1:已删除)
|
||||
*/
|
||||
private Integer delFlag;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.xiang.xservice.auth.service.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class XRolePermission {
|
||||
private Long roleId;
|
||||
private Long permissionId;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.xiang.xservice.auth.service.repository.mapper;
|
||||
|
||||
import com.xiang.xservice.auth.api.dto.resp.PermissionRoleDTO;
|
||||
import com.xiang.xservice.auth.service.entity.XPermission;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@Mapper
|
||||
public interface XPermissionMapper {
|
||||
|
||||
List<XPermission> getPermissionList(XPermission permission);
|
||||
|
||||
List<XPermission> getPermissionByIds(@Param("id") List<Long> ids);
|
||||
|
||||
XPermission getPermissionById(@Param("id") Long id);
|
||||
|
||||
int insert(XPermission permission);
|
||||
|
||||
int update(XPermission permission);
|
||||
|
||||
int delBatch(@Param("list") List<Long> ids, @Param("operator") String operator);
|
||||
|
||||
List<PermissionRoleDTO> loadAllPermission();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xiang.xservice.auth.service.repository.mapper;
|
||||
|
||||
import com.xiang.xservice.auth.service.entity.XRolePermission;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface XRolePermissionMapper {
|
||||
|
||||
List<XRolePermission> getRolePermissionsByRoleId(@Param("roleId") Long roleId);
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import com.xiang.xservice.auth.service.repository.mapper.XUserRoleMapper;
|
||||
import com.xiang.xservice.auth.service.service.XUserService;
|
||||
import com.xiang.xservice.basic.enums.DelStatusEnum;
|
||||
import com.xiang.xservice.basic.exception.BusinessException;
|
||||
import com.xiang.xservice.basic.utils.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
@@ -94,7 +93,7 @@ public class XUserServiceImpl implements XUserService {
|
||||
// 自定义 scope
|
||||
.claim("timestamp", System.currentTimeMillis())
|
||||
.claim("username", request.getUsername())
|
||||
.claim("roles", JsonUtils.toJsonString(roleCodes))
|
||||
.claim("authorities", roleCodes)
|
||||
.build();
|
||||
|
||||
// 2. 编码生成 token
|
||||
|
||||
170
xs-service/src/main/resources/mapper/user/XPermission.xml
Normal file
170
xs-service/src/main/resources/mapper/user/XPermission.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?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="com.xiang.xservice.auth.service.repository.mapper.XPermissionMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.xiang.xservice.auth.service.entity.XPermission" >
|
||||
<result column="id" property="id" />
|
||||
<result column="name" property="name" />
|
||||
<result column="code" property="code" />
|
||||
<result column="type" property="type" />
|
||||
<result column="parent_id" property="parentId" />
|
||||
<result column="api_path" property="apiPath" />
|
||||
<result column="method" property="method" />
|
||||
<result column="created_time" property="createdTime" />
|
||||
<result column="create_by" property="createBy" />
|
||||
<result column="updated_time" property="updatedTime" />
|
||||
<result column="update_by" property="updateBy" />
|
||||
<result column="del_flag" property="delFlag" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
parent_id,
|
||||
api_path,
|
||||
method,
|
||||
created_time,
|
||||
create_by,
|
||||
updated_time,
|
||||
update_by,
|
||||
del_flag
|
||||
</sql>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="com.xiang.xservice.auth.service.entity.XPermission">
|
||||
INSERT INTO x_permission
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="null != name and '' != name">
|
||||
name,
|
||||
</if>
|
||||
<if test="null != code and '' != code">
|
||||
code,
|
||||
</if>
|
||||
<if test="null != type ">
|
||||
type,
|
||||
</if>
|
||||
<if test="null != parentId ">
|
||||
parent_id,
|
||||
</if>
|
||||
<if test="null != apiPath and '' != apiPath">
|
||||
api_path,
|
||||
</if>
|
||||
<if test="null != method and '' != method">
|
||||
method,
|
||||
</if>
|
||||
<if test="null != createdTime ">
|
||||
created_time,
|
||||
</if>
|
||||
<if test="null != createBy and '' != createBy">
|
||||
create_by,
|
||||
</if>
|
||||
<if test="null != updatedTime ">
|
||||
updated_time,
|
||||
</if>
|
||||
<if test="null != updateBy and '' != updateBy">
|
||||
update_by,
|
||||
</if>
|
||||
<if test="null != delFlag ">
|
||||
del_flag
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="null != name and '' != name">
|
||||
#{name},
|
||||
</if>
|
||||
<if test="null != code and '' != code">
|
||||
#{code},
|
||||
</if>
|
||||
<if test="null != type ">
|
||||
#{type},
|
||||
</if>
|
||||
<if test="null != parentId ">
|
||||
#{parentId},
|
||||
</if>
|
||||
<if test="null != apiPath and '' != apiPath">
|
||||
#{apiPath},
|
||||
</if>
|
||||
<if test="null != method and '' != method">
|
||||
#{method},
|
||||
</if>
|
||||
<if test="null != createdTime ">
|
||||
#{createdTime},
|
||||
</if>
|
||||
<if test="null != createBy and '' != createBy">
|
||||
#{createBy},
|
||||
</if>
|
||||
<if test="null != updatedTime ">
|
||||
#{updatedTime},
|
||||
</if>
|
||||
<if test="null != updateBy and '' != updateBy">
|
||||
#{updateBy},
|
||||
</if>
|
||||
<if test="null != delFlag ">
|
||||
#{delFlag}
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="delBatch" >
|
||||
update x_permission set del_flag = 1, update_by = #{operator}, update_time = NOW()
|
||||
where id in
|
||||
<foreach collection="list" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="update" parameterType="com.xiang.xservice.auth.service.entity.XPermission">
|
||||
UPDATE x_permission
|
||||
<set>
|
||||
<if test="null != name and '' != name">name = #{name},</if>
|
||||
<if test="null != code and '' != code">code = #{code},</if>
|
||||
<if test="null != type ">type = #{type},</if>
|
||||
<if test="null != parentId ">parent_id = #{parentId},</if>
|
||||
<if test="null != apiPath and '' != apiPath">api_path = #{apiPath},</if>
|
||||
<if test="null != method and '' != method">method = #{method},</if>
|
||||
<if test="null != createdTime ">created_time = #{createdTime},</if>
|
||||
<if test="null != createBy and '' != createBy">create_by = #{createBy},</if>
|
||||
<if test="null != updatedTime ">updated_time = #{updatedTime},</if>
|
||||
<if test="null != updateBy and '' != updateBy">update_by = #{updateBy},</if>
|
||||
<if test="null != delFlag ">del_flag = #{delFlag}</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
<select id="getPermissionList" resultMap="BaseResultMap">
|
||||
select <include refid="Base_Column_List"/>
|
||||
from x_permission
|
||||
<trim prefix="AND">
|
||||
<where>
|
||||
del_flag = 0
|
||||
<if test="name != null and name != ''">name = #{name}</if>
|
||||
<if test="code != null and code != ''">code = #{code}</if>
|
||||
<if test="type != null">type = #{type}</if>
|
||||
<if test="method != null and method != ''">method = #{method}</if>
|
||||
<if test="apiPath != null and apiPath != ''">api_path = #{apiPath}</if>
|
||||
</where>
|
||||
</trim>
|
||||
</select>
|
||||
<select id="getPermissionByIds" resultMap="BaseResultMap">
|
||||
select <include refid="Base_Column_List"/>
|
||||
from x_permission
|
||||
where id in
|
||||
<foreach collection="list" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="getPermissionById" resultMap="BaseResultMap">
|
||||
select <include refid="Base_Column_List"/>
|
||||
from x_permission
|
||||
where id = #{id}
|
||||
</select>
|
||||
<select id="loadAllPermission" resultType="com.xiang.xservice.auth.api.dto.resp.PermissionRoleDTO">
|
||||
select p.api_path api_url, p.method, r.code as role_code
|
||||
from x_permission p
|
||||
join x_role_permission rp on p.id = rp.permission_id
|
||||
join x_role r on rp.role_id = r.id
|
||||
where p.del_flag = 0 and r.del_flag = 0 and r.status = 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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="com.xiang.xservice.auth.service.repository.mapper.XRolePermissionMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.xiang.xservice.auth.service.entity.XRolePermission" >
|
||||
<result property="roleId" column="role_id"/>
|
||||
<result property="permissionId" column="permission_id"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
role_id,
|
||||
permission_id
|
||||
</sql>
|
||||
<select id="getRolePermissionsByRoleId" resultMap="BaseResultMap">
|
||||
select <include refid="Base_Column_List"/>
|
||||
from x_role_permission
|
||||
where role_id = #{roleId}
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user