Spring Boot使用Spring Security

1.数据库建立用户、角色表: users authorities

2.使用JPA完成数据库表到domain类的映射,并且扩展 UserDetails

package com.lagou.springbootdemo.security;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Entity
@Table(name="`users`")
public class SpringCssUser implements UserDetails {

    private static  long serialVersionUID = 1L;
    private Long id;
    @Id
    private  String username;
    private  String password;
    private  String phoneNumber;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
//省略getter/setter

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public List<SimpleGrantedAuthority> getAuthorities() {
        return Collections.singletonList(new SimpleGrantedAuthority("ROLE_admin"));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

显然,这里我们使用了一种更简单的方法满足 UserDetails 中各个接口的实现需求。一旦我们构建了一个 SpringCssUser 类,就可以创建对应的表结构存储类中所定义的字段。同时,我们也可以基于 Spring Data JPA 创建一个自定义的 Repository,如下代码所示:

package com.lagou.springbootdemo.security;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository("springCssUserRepository")
public interface SpringCssUserRepository extends CrudRepository<SpringCssUser, String> {
    SpringCssUser findByUsername(String username);
}

SpringCssUserRepository 扩展了 CrudRepository 接口,并提供了一个方法名衍生查询 findByUsername。

3.扩展 UserDetailsService

接着,我们来实现 UserDetailsService 接口,如下代码所示:

@Service

public class SpringCssUserDetailsService implements UserDetailsService {

 
  @Autowired
  private SpringCssUserRepository repository;

  @Override
  public UserDetails loadUserByUsername(String username)
      throws UsernameNotFoundException {
    SpringCssUser user = repository.findByUsername(username);
    if (user != null) {
      return user;
    }
    throw new UsernameNotFoundException(
                    "SpringCSS User '" + username + "' not found");
  }
}

在 UserDetailsService 接口中,我们只需要实现 loadUserByUsername 方法就行。因此,我们可以基于 SpringCssUserRepository 的 findByUsername 方法,再根据用户名从数据库中查询数据。

4.实现接口完成定制化配置

最后,我们再次回到 SpringCssSecurityConfig 类。

这次我们将使用自定义的 SpringCssUserDetailsService 完成用户信息的存储和查询,此时我们只需要对配置策略做一些调整,调整后的完整 SpringCssSecurityConfig 类如下代码所示:

@Configuration

public class SpringCssSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    SpringCssUserDetailsService springCssUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 
         auth.userDetailsService(springCssUserDetailsService);

}

}

这里我们注入了 SpringCssUserDetailsService,并将其添加到 AuthenticationManagerBuilder 中,这样 AuthenticationManagerBuilder 将基于自定义的 SpringCssUserDetailsService 完成 UserDetails 的创建和管理。

5.对 HTTP 端点进行访问授权管理

在一个 Web 应用中,权限管理的对象是通过 Controller 层暴露的一个个 HTTP 端点,而这些 HTTP 端点就是需要授权访问的资源。

开发人员使用 Spring Security 中提供的一系列丰富技术组件,即可通过简单的设置对权限进行灵活管理。

 

使用配置方法

通过在 SpringCssSecurityConfig extends WebSecurityConfigurerAdapter类中重写方法实现。

实现访问授权的第一种方法是使用配置方法,关于配置方法的处理过程也是位于 WebSecurityConfigurerAdapter 类中,但使用的是 configure(HttpSecurity http) 方法,如下代码所示:

protected void configure(HttpSecurity http) throws Exception {
 http
            .authorizeRequests()
            .anyRequest()
            .hasRole("admin")
 .authenticated()
            .and()
            .formLogin()
 .and()
            .httpBasic();
}

注意:hasRole方法会在输入的参数基础上在前面加入前缀ROLE_,因此第四步赋值的role必须是ROLE_admin。hasAuthorities方法不会自动添加前缀,因此,赋予的权限名和方法的参数名必须一致。

上述代码就是 Spring Security 中作用于访问授权的默认实现方法,这里用到了多个常见的配置方法。

回想 18 课时中的内容,访问任何端点时,一旦在代码类路径中引入了 Spring Security 框架,就会弹出一个登录界面从而完成用户认证。因为认证是授权的前置流程,认证结束后就可以进入授权环节。

结合这些配置方法的名称,我们简单分析一下实现这种默认的授权效果的具体步骤。

首先,通过 HttpSecurity 类的 authorizeRequests() 方法,我们可以对所有访问 HTTP 端点的 HttpServletRequest 进行限制。

其次,anyRequest().authenticated() 语句指定了所有请求都需要执行认证,也就是说没有通过认证的用户无法访问任何端点。

然后,formLogin() 语句指定了用户需要使用表单进行登录,即会弹出一个登录界面。

最后, httpBasic() 语句使用 HTTP 协议中的 Basic Authentication 方法完成认证。

当然,Spring Security 中还提供了很多其他有用的配置方法供开发人员灵活使用,下表中我们进行了列举,一起来看下。

基于上表中的配置方法,我们就可以通过 HttpSecurity 实现自定义的授权策略。

比方说,我们希望针对“/orders”根路径下的所有端点进行访问控制,且只允许认证通过的用户访问,那么可以创建一个继承了 WebSecurityConfigurerAdapter 类的 SpringCssSecurityConfig,并覆写其中的 configure(HttpSecurity http) 方法来实现,如下代码所示:

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()

            .antMatchers("/orders/**")

            .authenticated();

    }

}

请注意:虽然上表中的这些配置方法非常有用,但是由于我们无法基于一些来自环境和业务的参数灵活控制访问规则,也就存在一定的局限性。

为此,Spring Security 还提供了一个 access() 方法,该方法允许开发人员传入一个表达式进行更细粒度的权限控制,这里,我们将引入Spring 框架提供的一种动态表达式语言—— SpEL(Spring Expression Language 的简称)。

只要 SpEL 表达式的返回值为 true,access() 方法就允许用户访问,如下代码所示:

@Override

public void configure(HttpSecurity http) throws Exception {

 

        http.authorizeRequests()

            .antMatchers("/orders")

            .access("hasRole('ROLE_USER')");

}

 

上述代码中,假设访问“/orders”端点的请求必须具备“ROLE_USER”角色,通过 access 方法中的 hasRole 方法我们即可灵活地实现这个需求。当然,除了使用 hasRole 外,我们还可以使用 authentication、isAnonymous、isAuthenticated、permitAll 等表达式进行实现。因这些表达式的作用与前面介绍的配置方法一致,我们就不过多赘述。

 

使用注解

除了使用配置方法,Spring Security 还为我们提供了 @PreAuthorize 注解实现类似的效果,该注解定义如下代码所示:

@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface PreAuthorize {

 

    //通过 SpEL 表达式设置访问控制

    String value();

}

 

可以看到 @PreAuthorize 的原理与前面介绍的 access() 方法一样,即通过传入一个 SpEL 表达式设置访问控制,如下所示代码就是一个典型的使用示例:

@RestController

@RequestMapping(value="orders")

public class OrderController {

 

@PostMapping(value = "/")

@PreAuthorize("hasRole(ROLE_ADMIN)")

public void addOrder(@RequestBody Order order) {

    …

}

}

 

从这个示例中可以看到,在“/orders/”这个 HTTP 端点上,我们添加了一个 @PreAuthorize 注解用来限制只有角色为“ROLE_ADMIN”的用户才能访问该端点。

其实,Spring Security 中用于授权的注解还有 @PostAuthorize,它与 @PreAuthorize 注解是一组,主要用于请求结束之后检查权限。因这种情况比较少见,这里我们不再继续展开,你可以翻阅相关资料学习。

6.实现多种授权方案

 

使用用户级别保护服务访问

我们创建了一个 SpringCssSecurityConfig 类继承 WebSecurityConfigurerAdapter,如下代码所示:

@Configuration

public class SpringCssSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override

    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()

            .anyRequest()

            .authenticated();
    }
}

位于 configure() 方法中的 .anyRequest().authenticated() 语句指定了访问 customer-service 下的所有端点的任何请求都需要进行验证。因此,当我们使用普通的 HTTP 请求访问 CustomerController 中的任何 URL(例如http://localhost:8083/customers/1),将会得到如下图代码所示的错误信息,该错误信息明确指出资源的访问需要进行认证。

  1. {

  2.     "error": "access_denied",

  3.     "error_description": "Full authentication is required to access to this resource"

  4. }

 

使用用户+角色级别保护服务访问

对于某些安全性要求比较高的 HTTP 端点,我们通常需要限定访问的角色。

例如,customer-service 服务中涉及客户工单管理等核心业务,我们认为不应该给所有的认证用户开放资源访问入口,而应该限定只有角色为“ADMIN”的管理员才开放。这时,我们就可以使用认证用户+角色保护服务的访问控制机制,具体的示例代码如下所示:

@Configuration

public class SpringCssSecurityConfig extends WebSecurityConfigurerAdapter {

 

    @Override

    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
            .antMatchers("/customers/**")
            .hasRole("ADMIN")
            .anyRequest()
            .authenticated();
    }
}

在上述代码中可以看到,我们使用了 HttpSecurity 类中的 antMatchers("/customer/") 和 hasRole("ADMIN") 方法为访问"/customers/"的请求限定了角色,只有"ADMIN"角色的认证用户才能访问以"/customers/"为根地址的所有 URL。

如果我们使用了认证用户+角色的方式保护服务访问,使用角色为“USER”的认证用户“springcss_user”访问 customer-service 时就会出现如下所示的“access_denied”错误信息:

  1. {

  2.     "error": "access_denied",

  3.     "error_description": "Access is denied"

  4. }

而我们使用具有“ADMIN”角色的“springcss_admin”用户访问 customer-service 时,将会得到正常的返回信息,关于这点你可以自己做一些尝试。

 

使用用户+角色+操作级别保护服务访问

最后一种保护服务访问的策略粒度划分最细,在认证用户+角色的基础上,我们需要再对具体的 HTTP 操作进行限制。

在 customer-service 中,我们认为所有对客服工单的删除操作都很危险,因此可以使用 http.antMatchers(HttpMethod.DELETE, "/customers/**") 方法对删除操作进行保护,示例代码如下:

@Configuration

public class SpringCssSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers(HttpMethod.DELETE, "/customers/**")
                .hasRole("ADMIN")
                .anyRequest()
                .authenticated();
    }

}

 

上述代码的效果在于对“/customers”端点执行删除操作时,我们需要使用具有“ADMIN”角色的“springcss_admin”用户,执行其他操作时不需要。因为如果我们使用“springcss_user”账户执行删除操作,还是会出现“access_denied”错误信息。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

fang·up·ad

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

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

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

打赏作者

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

抵扣说明:

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

余额充值