zoukankan      html  css  js  c++  java
  • 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  这篇博客写的非常清楚, 把原理分析的比较透彻, 可以接着学习底层的东西多一点。

  • 相关阅读:
    hdu 4849
    HDU4850 构造一个长度为n的串,要求任意长度为4的子串不相同
    2014 多校第一场官方题解。
    hdu4862 2014多校B题/ 费用流(最优情况下用不大于K条路径覆盖)(不同的解法)
    dp+分类讨论 Gym 101128E
    优先队列 逆向思维 Gym 101128C
    很好的脑洞题:dfs+暴力 Gym
    树dp...吧 ZOJ 3949
    扫描线(线段树)+贪心 ZOJ 3953
    dp ZOJ 3956
  • 原文地址:https://www.cnblogs.com/xumBlog/p/11565973.html
Copyright © 2011-2022 走看看