Spring Security:让你告别复杂的权限管理功能3

目录

1.SpringSecurity之授权

1.1.授权介绍

1.2.修改User配置角色和权限

1.3.修改loadUserByUsername方法

1.4.修改SpringSecurity配置类

1.5.控制Controller层接口权限

1.6.启动测试

1.7.异常处理

1.7.1.AccessDeniedHandler

1.7.2.AuthenticationEntryPoint


1.SpringSecurity之授权

1.1.授权介绍

Spring Security 中的授权分为两种类型:

  • 基于角色的授权:以用户所属角色为基础进行授权,如管理员、普通用户等,通过为用户分配角色来控制其对资源的访问权限。

  • 基于资源的授权:以资源为基础进行授权,如 URL、方法等,通过定义资源所需的权限,来控制对该资源的访问权限。

Spring Security 提供了多种实现授权的机制,最常用的是使用基于注解的方式,建立起访问资源和权限之间的映射关系。

其中最常用的两个注解是 @Secured@PreAuthorize@Secured 注解是更早的注解,基于角色的授权比较适用,@PreAuthorize 基于 SpEL 表达式的方式,可灵活定义所需的权限,通常用于基于资源的授权。

1.2.修改User配置角色和权限

方法1:定义SQL语句,根据用户ID查询角色和角色对应的权限。

  • select * from sys_user a,
                  sys_user_role b,
                  sys_role_module c,
                  sys_module d
    where a.id=b.user_id and
          b.role_id=c.role_id and
          c.module_id=d.id;

如果不知道表结构的同学可以参考上节课,里面有给出实例代码

https://blog.csdn.net/d2916172682/article/details/135177133?spm=1001.2014.3001.5502

1.3.修改loadUserByUsername方法

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
            implements UserService,UserDetailsService {
​
    @Autowired
    private RoleMapper roleMapper;
​
    @Autowired
    private RoleModuleMapper roleModuleMapper;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据库中用户信息
        User user = this.getOne(new QueryWrapper<User>().eq("username", username));
        //判断用户是否存在
        if(Objects.isNull(user))
            throw new UsernameNotFoundException("用户不存在");
        //权限校验TODO,后续讲解
        List<String> roles = roleMapper.queryRolesByUid(user.getId());
        List<String> permission = roleModuleMapper.queryRoleModuleByUid(user.getId());
        user.setRoles(roles);
        user.setPermissions(permission);
        return user;
    }   
}

方法2:修改loadUserByUsername方法 

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private IUserService userService;
    @Autowired
    private IUserRoleService userRoleService;
    @Autowired
    private IRoleModuleService roleModuleService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IModuleService moduleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
        if(user == null){
            throw new UsernameNotFoundException("用户名无效");
        }
        //先查询出所有身份
        //map 遍历所有的对象 返回新的数据回放到一个新的流中
        //collect 将流中的元素变成一个集合
        List<Integer> role_ids = userRoleService
                .list(new QueryWrapper<UserRole>().eq("user_id", user.getId()))
                .stream().map(UserRole::getRoleId)
                .collect(Collectors.toList());
        //根据身份id查询到名字字段
        List<String> roles = roleService
                .list(new QueryWrapper<Role>().in("role_id", role_ids))
                .stream().map(Role::getRoleName)
                .collect(Collectors.toList());
        //根据身份id查询到具备的权限id
        List<Integer> module_ids = roleModuleService
                .list(new QueryWrapper<RoleModule>().in("role_id", role_ids))
                .stream().map(RoleModule::getModuleId)
                .collect(Collectors.toList());
        //根据权限id查询到对应的权限
        List<String> modules = moduleService
                .list(new QueryWrapper<Module>().in("id", module_ids))
                .stream().map(Module::getUrl)
                .filter(s->s!=null)//过滤掉路径为null的内容
                .collect(Collectors.toList());
        //包含了身份和权限路径
        roles.addAll(modules);
        List<GrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        //将所有根据id查询到的身份和权限赋值给用户
        user.setAuthorities(authorities);
        return user;
    }
}

1.4.修改SpringSecurity配置类

当我们想要开启spring方法级安全时,只需要在任何 @Configuration实例上使用@EnableGlobalMethodSecurity 注解就能达到此目的。同时这个注解为我们提供了prePostEnabledsecuredEnabledjsr250Enabled 三种不同的机制来实现同一种功能。

修改WebSecurityConfig配置类,开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
    ...
}

@EnableGlobalMethodSecurity是Spring Security提供的一个注解,用于启用方法级别的安全性。它可以在任何@Configuration类上使用,以启用Spring Security的方法级别的安全性功能。它接受一个或多个参数,用于指定要使用的安全注解类型和其他选项。以下是一些常用的参数:

  • prePostEnabled:如果设置为true,则启用@PreAuthorize@PostAuthorize注解。默认值为false

  • securedEnabled:如果设置为true,则启用@Secured注解。默认值为false

  • jsr250Enabled:如果设置为true,则启用@RolesAllowed注解。默认值为false

  • proxyTargetClass:如果设置为true,则使用CGLIB代理而不是标准的JDK动态代理。默认值为false

使用@EnableGlobalMethodSecurity注解后,可以在应用程序中使用Spring Security提供的各种注解来保护方法,例如@Secured@PreAuthorize@PostAuthorize@RolesAllowed。这些注解允许您在方法级别上定义安全规则,以控制哪些用户可以访问哪些方法。

注解介绍:

注解说明
@PreAuthorize用于在方法执行之前对访问进行权限验证
@PostAuthorize用于在方法执行之后对返回结果进行权限验证
@Secured用于在方法执行之前对访问进行权限验证
@RolesAllowed是Java标准的注解之一,用于在方法执行之前对访问进行权限验证

1.5.控制Controller层接口权限

@RestController
@RequestMapping("/user")
public class UserController {
​
    @Autowired
    private RoleService roleService;
​
    @Autowired
    private ModuleService moduleService;
​
    @RequestMapping("/userLogin")
    public String userLogin(User user){
        return "login";
    }
​
    @PreAuthorize("hasAuthority('order:manager:list')")
    @GetMapping("/queryRoles")
    public JsonResponseBody<List<Role>> queryRoles(){
        List<Role> list = roleService.list();
        return new JsonResponseBody<>(list);
    }
​
    @PreAuthorize("hasAuthority('book:manager:list')")
    @GetMapping("/queryModules")
    public JsonResponseBody<List<Module>> queryModules(){
        List<Module> list = moduleService.list();
        return new JsonResponseBody<>(list);
    }
​
    @PreAuthorize("hasAuthority('管理员')")
    @GetMapping("/queryTest")
    public JsonResponseBody<?> queryTest(){
        return new JsonResponseBody<>("你好,我是管理员!");
    }
}

常见内置表达式

1.6.启动测试

配置完毕之后,重新启动项目。分别使用两个不同的用户(adminzs)登录进行权限测试。

注意:admin具备所有权限;zs只具备部分权限。

当通过zs用户登录成功之后,点击权限和角色验证进行权限测试。

  • 点击获取用户角色信息,可以成功显示数据:

  • 点击获取角色权限信息,提示403错误:(也就是无权限提示)

噢耶,权限验证成功,只是空白错误页面显示有点。。。

1.7.异常处理

1.7.1.AccessDeniedHandler

AccessDeniedHandler是Spring Security提供的一个接口,用于处理访问被拒绝的情况。当用户尝试访问受保护资源但没有足够的权限时,Spring Security会调用AccessDeniedHandler来处理这种情况。

AccessDeniedHandler接口只有一个方法handle(),该方法接收HttpServletRequestHttpServletResponseAccessDeniedException三个参数。在handle()方法中,可以自定义响应的内容,例如返回一个自定义的错误页面或JSON响应。

创建AccessDeniedHandlerImpl类并实现AccessDeniedHandler接口,实现自定义的JSON响应。例如:

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IUserService userService;


    //redis
    // login:admin:1
    // incr 自动增加
    //或者判断值是否大于三 如果大于三 直接修改
    @Override
    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) throws IOException, ServletException {
        if(1==2){
            User user = userService.getOne(new QueryWrapper<User>().eq("username", "admin"));
            //设置他的账户过期  他就无法登录了  如果想要解锁的话联系管理员
            user.setAccountNonLocked(false);
            userService.updateById(user);
        }
        objectMapper
                .writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));
    }
}

在这里我们使用了JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE) 也就是枚举enum 所以还需要导入两个类 

@Getter
public enum JsonResponseStatus {

    OK(200, "OK"),
    UN_KNOWN(500, "未知错误"),
    RESULT_EMPTY(1000, "查询结果为空!"),
    NO_ACCES(3001, "没有权限!"),
    NO_LOGIN(4001, "没有登录!"),
    LOGIN_FAILURE(5001, "账号或者密码错误!"),
    ;

    private final Integer code;
    private final String msg;

    JsonResponseStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

}

@Data
public class JsonResponseBody<T> {

    private Integer code;
    private String msg;
    private T data;
    private Long total;

    private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data) {
        this.code = jsonResponseStatus.getCode();
        this.msg = jsonResponseStatus.getMsg();
        this.data = data;
    }

    private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data, Long total) {
        this.code = jsonResponseStatus.getCode();
        this.msg = jsonResponseStatus.getMsg();
        this.data = data;
        this.total = total;
    }

    public static <T> JsonResponseBody<T> success() {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, null);
    }

    public static <T> JsonResponseBody<T> success(T data) {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, data);
    }

    public static <T> JsonResponseBody<T> success(T data, Long total) {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, data, total);
    }

    public static <T> JsonResponseBody<T> unknown() {
        return new JsonResponseBody<T>(JsonResponseStatus.UN_KNOWN, null);
    }

    public static <T> JsonResponseBody<T> other(JsonResponseStatus jsonResponseStatus) {
        return new JsonResponseBody<T>(jsonResponseStatus, null);
    }

}

然后,将自定义的accessDeniedHandler注入到Spring Security的配置中:

@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
​
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return
        http
        // ...
        .exceptionHandling()
            .accessDeniedHandler(accessDeniedHandler())
        // ...
}

这样,当访问被拒绝时,Spring Security就会调用自定义AccessDeniedHandler来处理。

1.7.2.AuthenticationEntryPoint

AuthenticationEntryPoint是Spring Security中的一个接口,用于定义如何处理未经身份验证的请求。当用户尝试访问需要身份验证的资源但未进行身份验证时,AuthenticationEntryPoint将被调用。

在这个接口中,可以自定义如何处理这些未经身份验证的请求,例如重定向到登录页面或返回错误消息。需要注意的是,AuthenticationEntryPoint只处理未经身份验证的请求,已经进行身份验证但权限不足的请求则需要使用AccessDeniedHandler来处理。

创建AuthenticationEntryPointImpl类并实现AuthenticationEntryPoint接口,实现自定义的JSON响应。例如:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setStatus(200);
        int code = 500;
        String msg = "认证失败,无法访问系统资源";
        response.setContentType("application/json;charset=UTF-8");
        Map<String, Object> result = new HashMap<>();
        result.put("msg", msg);
        result.put("code", code);
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

然后,将自定义的authenticationEntryPoint注入到Spring Security的配置中:

@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;


@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return
        http
        // ...
        .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
        // ...
}

这样,当认证失败时,Spring Security就会调用自定义AuthenticationEntryPoint来处理。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以。Spring Security 是一个开源安全框架,可以为 Java 应用程序提供认证、授权和其他安全功能。它可以集成到各种 Web 框架中,如 Spring MVC、Spring Boot 等,也可以与其他框架一起使用。下面是使用 Spring Security 实现用户权限管理的一些步骤: 1. 添加 Spring Security 依赖:在 Maven 或 Gradle 中添加 Spring Security 依赖,可以通过以下方式添加: ```xml <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.5.1</version> </dependency> ``` 2. 配置 Spring Security:在 Spring 配置文件中配置 Spring Security,包括用户认证、授权、登录、注销等相关配置。 3. 实现用户认证:通过实现 UserDetailsService 接口,重写 loadUserByUsername 方法,查询数据库或其他数据源中的用户信息,进行认证。 4. 实现用户授权:通过实现 AccessDecisionVoter 接口和 AccessDecisionManager 接口,重写对应的方法,实现用户权限的控制逻辑。 5. 配置登录页面和注销页面:在 Spring Security 配置文件中配置登录页面和注销页面的 URL。 6. 配置安全过滤器链:在 Spring Security 配置文件中配置安全过滤器链,包括登录过滤器、授权过滤器等。 7. 配置 CSRF 防护:在 Spring Security 配置文件中配置 CSRF 防护,以防止 CSRF 攻击。 以上是使用 Spring Security 实现用户权限管理的一些基本步骤。需要注意的是,具体实现方式会根据具体业务需求和系统架构而有所不同。可以参考 Spring Security 官方文档和相关书籍,了解更多详细的实现细节和最佳实践。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值