Spring security 方法级权限控制

环境

springboot:1.5
Intellij IDEA:2021.1

序言

以前只是用到架构师搭好的环境,具体怎么配置,怎么用并不是很清楚;
最近因为项目的原因,研究了下,虽然最终没有用上,但是研究的成果,我得记录下来;

步骤

这里假设已经是springboot项目

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

放开URL

登录这块,公司有统一的SSO,并不需要用到spring security的登录控制。
所以我需要对所有的URL进行开放。只想控制方法层。

创建配置类WebSecurityConfig

package com.sgy.securitydemo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
//开启Spring Security的功能
@EnableWebSecurity
//添加@EnableGlobalMethodSecurity注解开启Spring方法级安全
//prePostEnabled属性决定Spring Security的前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private MyPermissionEvaluator myPermissionEvaluator;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        /*
         * 在内存中配置两个用户 admin和user
         */
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("admin");

        auth.inMemoryAuthentication()
                .withUser("user")
                .password(passwordEncoder().encode("123456"))
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定义哪些URL需要被保护、哪些不需要被保护
        http.authorizeRequests()
                // 设置所有人都可以访问登录页面
                .antMatchers("/**").permitAll();
                // 任何请求,登录后可以访问
//                .anyRequest().authenticated()
//                .and()
//                .formLogin().loginPage("/");

        http.authorizeRequests().expressionHandler(defaultWebSecurityExpressionHandler());
    }

	/**
     * 为了将自定义的myPermissionEvaluator注入给spring security
     * @return
     */
    @Bean
    public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(myPermissionEvaluator);
        return handler;
    }

    /**
     * 解决跨域问题
     * @return
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.addAllowedOrigin(CorsConfiguration.ALL);
        configuration.addAllowedHeader(CorsConfiguration.ALL);
        configuration.addAllowedMethod(CorsConfiguration.ALL);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /*
     * 指定加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

使用@PreAuthorize进行方法控制

@RestController
public class HelloController {

    @GetMapping("/helloAdmin")
    @PreAuthorize("hasPermission('', 'user:view')")
    public String helloAdmin() {
        return "I am Admin";
    }
}

在这里插入图片描述

通过查看源码我们知道:

public boolean hasPermission(Object target, Object permission) {
   return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
}

因为没有使用spring security的登录功能,所以this.authentication肯定就是匿名用户,后面两个:targetpermission参数,就是我们在使用@PreAuthorize("hasPermission('', 'user:view')")注解时,传的两个字符串。target我们传'',permission我们传的是user:view(自己定义的)。

接着看源码我们发现permissionEvaluator.hasPermission()方法有个默认实现:

public boolean hasPermission(Authentication authentication, Object target, Object permission) {
    this.logger.warn(LogMessage.format("Denying user %s permission '%s' on object %s", authentication.getName(), permission, target));
    return false;
}

只是打印了下日志,并且始终返回false

这也就意味着我们需要重写hasPermission()方法。

重写hasPermission()方法

重写hasPermission()方法方法需要实现PermissionEvaluator接口。

package com.sgy.securitydemo.config;

import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;

@Component
public class MyPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object o, Object o1) {
		//假设是HTTP请求,那么用户ID问问需要到HttpServletRequest中去获取。
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        System.out.println(o + "-=-=-=-=" + o1 + authentication);
        //这里写业务判断来决定是否返回true or false
        return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
        return false;
    }
}

实际业务开发:我们先去用户权限表中查询该用户权限,然后比较该用户是否拥有该方法的权限。
方法的权限就是我们配置@PreAuthorize("hasPermission('', 'user:view')")中的user:view

全局异常

当权限不通过时,我们往往会抛出业务异常。
这时我们还需要一个全局异常捕获:

package com.sgy.securitydemo.config;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionRespone {

    /**
     * 全局异常捕获
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public Object errorHandler( Exception e) {
    	//这里写的很简单,实际上应该转成业务对象返回出去。
        e.printStackTrace();
        return null;
    }

	//具体业务写法参考:
	/**
     * 捕获所有异常
     */
    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public ResponseEntity<?> handleAllException(HttpServletRequest request,
                                                HttpServletResponse response,
                                                Throwable error) {

        JsonResult json = exceptionHandler.handleException(request, response, error);
        //401 error
        if (error instanceof AccessDeniedException) {
            json.setErrMessage(Optional.of("您没有权限,请联系管理员"));
            json.setErrCode(Optional.of(SC_UNAUTHORIZED));
            response.setStatus(SC_UNAUTHORIZED);
        }
        ResponseEntity.BodyBuilder resp = ResponseEntity.status(response.getStatus());
        if (_isJsonRequest(request)) {
            return resp.body(json);
        } else {
            // 如有必要,可以 Forward 到一个错误页面;或者定义一个 ErrorController 和 error 页面。
            String errMsg = String.format("出错啦:%s", json.getErrMessage().orElse("未知错误!"));
            return resp.body(errMsg);
        }
    }
}

总结

①方法级控制,本质上也是AOP控制,在方法执行前进行AOP处理。
只不过spring security帮我们做了很多。比如提供@PreAuthorize注解和hasPermission()方法等等。
②重点是重写hasPermission()方法这一步,里面包含的是我们的业务。

直接参考地址

pringBoot整合Spring Security安全框架(一)

spingsecurity中haspermission用法以及配置自定义PermissionEvaluator

间接参考地址

spring security下的跨域问题

Spring Security下解决跨域问题

springboot全局异常捕获

Spring Security基于方法级别的自定义表达式(可以完成任何权限判断)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值