Spring Security 入门原理及实战

参考博客: https://www.cnblogs.com/demingblog/p/10874753.html

Spring Security 是spring项目之中的一个安全模块,可以非常方便与spring项目无缝集成。

特别是在spring boot项目中加入spring security更是十分简单, 这里就简单介绍一下入门案例和使用原理吧!

写一个简单的springboot测试例子:

@Controller
public class AppController {

    @RequestMapping("/hello")
    @ResponseBody
    String home() {
        return "Hello ,spring security!";
    }
}

在springboot中的pom.xml中引入springsecurity, 加入如下代码:

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

然后在配置文件中写入

security.basic.enabled=false

spring security 默认提供了表单登录的功能。我们新建一个类SecurityConfiguration,并加入一些代码,如下所示:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }
}

上面的代码其实就是 一种配置,authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护。 formLogin() 定义当需要用户登录时候,转到的登录页面。

此时,我们并没有写登录页面,但是spring security默认提供了一个登录页面,以及登录控制器。我们这里简单写一个自定义的登录页面:

<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
    <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
    <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
    <input name="_csrf" type="hidden" value="635780a5-6853-4fcd-ba14-77db85dbd8bd" />
</table>
</form></body></html>

我们可以发现,这里有个form 。action="/login",这个/login依然是spring security提供的。form表单提交了三个数据:

  • username 用户名
  • password 密码
  • _csrf CSRF保护方面的内容,暂时先不展开解释

再配置文件加入如下:

security.user.name=admin
security.user.password=admin

重启项目,访问被保护的/hello页面。自动跳转到了spring security 默认的登录页面,我们输入用户名admin密码admin。点击Login按钮。会发现登录成功并跳转到了/hello。

除了登录,spring security还提供了rememberMe功能,这里不做过多解释

通常情况下,我们需要实现“特定资源只能由特定角色访问”的功能。假设我们的系统有如下两个角色:

  • ADMIN 可以访问所有资源
  • USER 只能访问特定资源

现在我们给系统增加“/product” 代表商品信息方面的资源(USER可以访问);增加"/admin"代码管理员方面的资源(USER不能访问)。代码如下:

@Controller
@RequestMapping("/product")
public class ProductTestController {

    @RequestMapping("/info")
    @ResponseBody
    public String productInfo(){
        return " some product info ";
    }
}
-------------------------------------------
@Controller
@RequestMapping("/admin")
public class AdminTestController {

    @RequestMapping("/home")
    @ResponseBody
    public String productInfo(){
        return " admin home page ";
    }
}
@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("admin1") // 管理员,同事具有 ADMIN,USER权限,可以访问所有资源
                    .password("admin1")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("user1").password("user1") // 普通用户,只能访问 /product/**
                    .roles("USER");
    }

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/product/**").hasRole("USER")
                    .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }

通过上面的配置, 可以简单实现一个springsecurity的小例子. 下面说一下springsecurity的原理:

Spring Security的核心组件

spring security核心组件有:SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager,下面分别介绍。

SecurityContext

安全上下文,用户通过Spring Security 的校验之后,验证信息存储在SecurityContext中,SecurityContext的接口定义如下:

public interface SecurityContext extends Serializable {
    /**
     * Obtains the currently authenticated principal, or an authentication request token.
     *
     * @return the <code>Authentication</code> or <code>null</code> if no authentication
     * information is available
     */
    Authentication getAuthentication();

    /**
     * Changes the currently authenticated principal, or removes the authentication
     * information.
     *
     * @param authentication the new <code>Authentication</code> token, or
     * <code>null</code> if no further authentication information should be stored
     */
    void setAuthentication(Authentication authentication);
}

SecurityContextHolder

SecurityContextHolder看名知义,是一个holder,用来hold住SecurityContext实例的。其作用就是存储当前认证信息。
在典型的web应用程序中,用户登录一次,然后由其会话ID标识。服务器缓存持续时间会话的主体信息。
在Spring Security中,在请求之间存储SecurityContext的责任落在SecurityContextPersistenceFilter上,
默认情况下,该上下文将上下文存储为HTTP请求之间的HttpSession属性。
它会为每个请求恢复上下文SecurityContextHolder,并且最重要的是,在请求完成时清除SecurityContextHolder。
SecurityContextHolder是一个类,他的功能方法都是静态的(static)。

SecurityContextHolder可以设置指定JVM策略(SecurityContext的存储策略),这个策略有三种:
1. MODE_THREADLOCAL:SecurityContext 存储在线程中。
2. MODE_INHERITABLETHREADLOCAL:SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
3. MODE_GLOBAL:SecurityContext 在所有线程中都相同。
SecurityContextHolder默认使用MODE_THREADLOCAL模式,即存储在当前线程中。在spring security应用中,我们通常能看到类似如下的代码:
SecurityContextHolder.getContext().setAuthentication(token);

Authentication

authentication 直译过来是“认证”的意思,在Spring Security 中Authentication用来表示当前用户是谁,一般来讲你可以理解为authentication就是一组用户名密码信息。Authentication也是一个接口,其定义如下:

public interface Authentication extends Principal, Serializable {
 
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

接口有4个get方法,分别获取

  • Authorities, 填充的是用户角色信息。
  • Credentials,直译,证书。填充的是密码。
  • Details ,用户信息。
  • Principal 直译,形容词是“主要的,最重要的”,名词是“负责人,资本,本金”。感觉很别扭,所以,还是不翻译了,直接用原词principal来表示这个概念,其填充的是用户名。

因此可以推断其实现类有这4个属性。这几个方法作用如下:

  • getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
  • getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
  • getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)
  • getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。
  • isAuthenticated: 获取当前 Authentication 是否已认证。
  • setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。

UserDetails

UserDetails,看命知义,是用户信息的意思。其存储的就是用户信息,其定义如下:

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

方法含义如下:

  • getAuthorites:获取用户权限,本质上是用户的角色信息。
  • getPassword: 获取密码。
  • getUserName: 获取用户名。
  • isAccountNonExpired: 账户是否过期。
  • isAccountNonLocked: 账户是否被锁定。
  • isCredentialsNonExpired: 密码是否过期。
  • isEnabled: 账户是否可用。

UserDetailsService

提到了UserDetails就必须得提到UserDetailsService, UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,他可以用来获取UserDetails。

通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);方法。

我们在实现loadUserByUsername方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,

(通常是一个org.springframework.security.core.userdetails.User,它继承自UserDetails) 并返回。

在实现loadUserByUsername方法的时候,如果我们通过查库没有查到相关记录,需要抛出一个异常来告诉spring security来“善后”。

这个异常是org.springframework.security.core.userdetails.UsernameNotFoundException

AuthenticationManager

AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下:

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

AuthenticationManager 的作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是一个抽象类,

因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException,LockedException,BadCredentialsException等。

BadCredentialsException可能会比较常见,即密码错误的时候。

总结:

SpringSecurity是个非常不错的框架,是个颗粒级别的验证框架,和springboot融合的非常完美, 值得学习。

https://www.cnblogs.com/demingblog/p/10874753.html  这篇博客写的非常清楚, 把原理分析的比较透彻, 可以接着学习底层的东西多一点。

posted on 2019-09-22 09:45  努力做一个伪程序员  阅读(738)  评论(0编辑  收藏  举报

导航