SpringBootrpc

SpringBoot与Spring-Security的集成

2019-01-07  本文已影响13人  意识流丶

Spring Security简介

Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。保护基于Spring的应用程序。Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求
开发文档:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/
API文档:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/api/

特点:

1.对身份验证和授权的全面和可扩展的支持
2.防止会话固定,点击劫持,跨站点请求伪造等攻击
3.Servlet API集成
4.可与Spring Web MVC集成

与SpringBoot进行集成

引入依赖

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

测试接口

@RestController
public class controller {
    @RequestMapping("/")
    public String home() {
        return "hello spring boot";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

开启提供基于web的Security配置

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
                .and()
                .logout().permitAll()
                .and()
                .formLogin();
        http.csrf().disable();
    }
}

@EnableWebMvcSecurity:开启Spring Security的功能
WebSecurityConfigurerAdapter:重写里面的方法来设置一些web安全的细节(主要通过重写configure())

WebSecurityConfigurerAdapterconfigure方法的源码
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }
    public void configure(WebSecurity web) throws Exception {
    }

    protected void configure(HttpSecurity http) throws Exception {
        this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
        ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
    }
    ......
}

configure(AuthenticationManagerBuilder auth)配置在内存中进行注册公开内存的身份验证
configure(WebSecurity web)配置拦截资源,例如过滤掉css/js/images等静态资源
configure(HttpSecurity http)定义需要拦截的URL

HttpSecurity具体使用可以参考
https://blog.csdn.net/dawangxiong123/article/details/68960041

接下来启动测试下
访问http://localhost:8080/ 没问题,因为配置了antMatchers("/")

image.png

访问http://localhost:8080/hello 就提示需要登录

image.png

配置基于内存的身份验证

重写configure(AuthenticationManagerBuilder auth)方法,设置用户名和密码还有角色

    @Bean
    public BCryptPasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.inMemoryAuthentication()
             .withUser("admin")
             .password(PasswordEncoder().encode("123456"))
             .roles("ADMIN");
    }

注:SpringBoot2.x后需要使用BCrypt强哈希方法来加密密码,如果不加的话登录不上并且控制台会有警告Encoded password does not look like BCrypt

再次访问http://localhost:8080/hello ,输入设置好的用户名和密码

image.png

根据角色做接口权限设置

在启动类上加入@EnableGlobalMethodSecurity(prePostEnabled = true)来实现授权,实现角色对某个操作是否有权限的控制.
@EnableGlobalMethodSecurity源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
    boolean prePostEnabled() default false;
    boolean securedEnabled() default false;
    boolean jsr250Enabled() default false;
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default 2147483647;
}
Spring Security默认是禁用注解的

1.prePostEnabled:支持Spring EL表达式,开启后可以使用
@PreAuthorize:方法执行前的权限验证
@PostAuthorize:方法执行后再进行权限验证
@PreFilter:方法执行前对集合类型的参数或返回值进行过滤,移除使对应表达式的结果为false的元素
@PostFilter:方法执行后对集合类型的参数或返回值进行过滤,移除使对应表达式的结果为false的元素

2.secureEnabled : 开启后可以使用
@Secured:用来定义业务方法的安全性配置属性列表

3.jsr250Enabled :支持JSR标准,开启后可以使用
@RolesAllowed:对方法进行角色验证
@DenyAll:允许所有角色调用
@PermitAll:不允许允许角色调用

controller层做相应的配置
@RestController
public class controller {
    @RequestMapping("/")
    public String home() {
        return "hello spring boot";
    }

    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping("/roleAuth")
    public String role() {
        return "admin auth";
    }
}

@PreAuthorize("hasRole('ROLE_ADMIN')"):访问前对角色做校验,只有ADMIN的角色才能访问

Spring-Security基于表达式的权限控制

Spring Security允许我们在定义URL访问或方法访问所应有的权限时使用Spring EL表达式
Spring Security可用表达式对象的基类是SecurityExpressionRoot
SecurityExpressionRoot源码

public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
    protected final Authentication authentication;
    private AuthenticationTrustResolver trustResolver;
    private RoleHierarchy roleHierarchy;
    private Set<String> roles;
    private String defaultRolePrefix = "ROLE_";
    public final boolean permitAll = true;
    public final boolean denyAll = false;
    private PermissionEvaluator permissionEvaluator;
    public final String read = "read";
    public final String write = "write";
    public final String create = "create";
    public final String delete = "delete";
    public final String admin = "administration";

    public SecurityExpressionRoot(Authentication authentication) {
        if (authentication == null) {
            throw new IllegalArgumentException("Authentication object cannot be null");
        } else {
            this.authentication = authentication;
        }
    }

    public final boolean hasAuthority(String authority) {
        return this.hasAnyAuthority(authority);
    }

    public final boolean hasAnyAuthority(String... authorities) {
        return this.hasAnyAuthorityName((String)null, authorities);
    }

    public final boolean hasRole(String role) {
        return this.hasAnyRole(role);
    }

    public final boolean hasAnyRole(String... roles) {
        return this.hasAnyAuthorityName(this.defaultRolePrefix, roles);
    }

    private boolean hasAnyAuthorityName(String prefix, String... roles) {
        Set<String> roleSet = this.getAuthoritySet();
        String[] var4 = roles;
        int var5 = roles.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            String role = var4[var6];
            String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
            if (roleSet.contains(defaultedRole)) {
                return true;
            }
        }

        return false;
    }

    public final Authentication getAuthentication() {
        return this.authentication;
    }

    public final boolean permitAll() {
        return true;
    }

    public final boolean denyAll() {
        return false;
    }

    public final boolean isAnonymous() {
        return this.trustResolver.isAnonymous(this.authentication);
    }

    public final boolean isAuthenticated() {
        return !this.isAnonymous();
    }

    public final boolean isRememberMe() {
        return this.trustResolver.isRememberMe(this.authentication);
    }

    public final boolean isFullyAuthenticated() {
        return !this.trustResolver.isAnonymous(this.authentication) && !this.trustResolver.isRememberMe(this.authentication);
    }

    public Object getPrincipal() {
        return this.authentication.getPrincipal();
    }

    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
        this.trustResolver = trustResolver;
    }

    public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
        this.roleHierarchy = roleHierarchy;
    }

    public void setDefaultRolePrefix(String defaultRolePrefix) {
        this.defaultRolePrefix = defaultRolePrefix;
    }

    private Set<String> getAuthoritySet() {
        if (this.roles == null) {
            this.roles = new HashSet();
            Collection<? extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities();
            if (this.roleHierarchy != null) {
                userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
            }

            this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
        }

        return this.roles;
    }

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

    public boolean hasPermission(Object targetId, String targetType, Object permission) {
        return this.permissionEvaluator.hasPermission(this.authentication, (Serializable)targetId, targetType, permission);
    }

    public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
        this.permissionEvaluator = permissionEvaluator;
    }

    private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
        if (role == null) {
            return role;
        } else if (defaultRolePrefix != null && defaultRolePrefix.length() != 0) {
            return role.startsWith(defaultRolePrefix) ? role : defaultRolePrefix + role;
        } else {
            return role;
        }
    }
}

角色默认前缀是ROLE_
hasAuthority([auth]):等同于hasRole
hasAnyAuthority([auth1,auth2]):等同于hasAnyRole
hasRole([role]):当前用户是否拥有指定角色。
hasAnyRole([role1,role2]):多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true
Principle:代表当前用户的principle对象
authentication:直接从SecurityContext获取的当前Authentication对象
permitAll():总是返回true,表示允许所有的
denyAll():总是返回false,表示拒绝所有的
isAnonymous():当前用户是否是一个匿名用户
isAuthenticated():表示当前用户是否已经登录认证成功了
isRememberMe():表示当前用户是否是通过Remember-Me自动登录的
isFullyAuthenticated():如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true
hasPermission():当前用户是否拥有指定权限

更多关于Spring-Security基于表达式的权限控制可以参考:
https://my.oschina.net/liuyuantao/blog/1924776

访问http://localhost:8080/roleAuth接口时只有ADMIN角色可以访问,其他角色访问会报Forbidden403

image.png

注:Spring-Security严格区分大小写

总结:

Spring-Security的角色权限验证主要就是用到hasRole()hasPermission()

前后端分离下,需要在接口上面做认证
具体实现参考:https://blog.csdn.net/cloume/article/details/83790111
前后端不分离的话,需要在前端展示页面上做下角色权限校验设置,原理都一样
具体实现参考:https://www.jianshu.com/p/155ec4272aa4

上一篇下一篇

猜你喜欢

热点阅读