SpringBoot与Spring-Security的集成

Spring Security简介

Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。保护基于Spring的应用程序。Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求
Github地址:https://github.com/spring-projects/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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容