美文网首页SpringBootrpc
SpringBoot与Spring-Security的集成

SpringBoot与Spring-Security的集成

作者: 意识流丶 | 来源:发表于2019-01-07 00:18 被阅读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

    相关文章

      网友评论

        本文标题:SpringBoot与Spring-Security的集成

        本文链接:https://www.haomeiwen.com/subject/hmyerqtx.html