User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   SpringSecurity   ›   正文
SpringSecurity

SpringSecurity—基础知识点与流程介绍

2020-07-15 18:24:54
1338  0 0
参考目录 隐藏
1) 一、Authentication,AuthenticationManager,AuthenticationProvider
2) 二、UserDetails,UserDetailsService,UserCache,User
3) 三、SecurityContext,SecurityContextHolder
4) 四、WebSecurityConfigurerAdapter
5) 总结
6) 相关笔记:

阅读完需:约 13 分钟

上次的关于SpringSecurity的几个关键词整理比较散乱,是从表层应用出发的流程和所用到的类,这篇笔记是上一篇的扩充,更加的清晰。

SpringSecurity的几个重要词(简单的登录流程)

首先回想一下我们最早学习java web的时候,是怎么做登录认证的。

  1. 首先制作登录页面(login.html)和登录认证endpoint(LoginController)
  2. 填写用户名、密码后Post提交表单给LoginController进行认证
  3. java后台编写UserService、UserDao,根据用户名、密码搜索数据库判断用户信息。如果符合条件,则将用户信息存入session,并设置cookie存储jsessionid,失效时间为半小时,跳转到主页;如果不符合条件重新定位到login.html
  4. 用户登录成功后,带着cookie横行无阻,因为自定义过滤器UserFilter判断session中是否有用户信息,有就放行
  5. 用户执行注销操作,java后台清除session和cookie,并跳转到登录页面。

Spring Security其实就是帮我们封装了一些类,简化了我们的代码。如果按照Spring Security的思路来做登录认证,应该是下面这样

  1. 用户编写WebSecurityConfigurerApdater的继承类,配置HttpSecurity,包括formLogin,antMatcher,hasRole等等。
  2. 项目启动自动装配FilterChain,访问不同uri对应不同的Filter Chain。
  3. 用户输入账号、密码点击登录,FilterChainProxy中的UsernamePasswordAuthenticationFilter获取request中的用户名、密码,验证身份信息
  4. doFilter()过程中会执行ProviderManager.authenticate(),即遍历所有AuthenticationProvider执行authenticate()方法。
  5. authenticate()方法中会调用userDetailService,用户自定义类继承UserDetailService,并重写其中的方法loadUserByUsername(),从数据库中获取用户信息进行比对
  6. 比对成功后将用户信息和角色信息整合成Authentication,并存入SecurityContext中,同时将SecurityContext也存入session中,跳转到主页面。
  7. 比对失败,SecurityContext中没有Authentication,FilterChain进行到最后一步FilterSecurityInterceptor,判断用户角色是否能访问request中的访问地址即资源。如果不行则报错跳转到指定页面;如果成功则进入request调用的资源。
  8. 注销操作由LogoutFilter执行,执行session.invalidate()和SecurityContextHolder.clearContext()。

Spring Security的核心思想是用户授权和资源认证。认证访问系统的用户,而授权则是用户可以访问的资源

认证是调用authenticationManager.authenticate()方法来获得证书authentication,一般我们采用用户名、密码方式认证,那么authentication的实现类就是UsernamePasswordAuthentication。

授权是让用户可以访问哪些资源,一般在WebSecurityConfigurerApdater的继承类中编写。

authorizeRequests().antMatchers("/static/","/webjars/","/resources/**").permitAll()

整个spring security的核心思想是:通过用户名、密码验证后获取信息Authentication,将此信息存入session中保存。以后每次访问都通过session中的属性SPRING_SECURITY_CONTEXT获取Authentication作为通行证。

上面简单介绍了Spring Security的验证流程,接下来我们具体讲解各个关键知识点。

一、Authentication,AuthenticationManager,AuthenticationProvider

Authentication代表证书,有了这个证书我们就无需每次请求都要重新认证身份信息,证书是对用户信息的一个简单封装。

Authentication的继承关系大致如下,其中Principal代表用户主体的概念,如用User,login id或者username代表一个entity,主要方法有equals()和getName();

Authentication用于存储身份验证信息,接口内容如下,包括getAuthorities(),getDetails(),getPrinciple(),getCredentials()以及isAuthenticated()。

注意Authentication是没有失效时间的!!!

public interface Authentication extends Principal, Serializable {
        /**
        *   一般在JPAEntity中继承UserDetail,重写该方法,
        *   存储验证信息Authority集合
        *   GrantedAuthority是个接口,有方法 String getAuthority();
        *   GrantedAuthority代表授权,a representation of the granted authority,
        *   getAuthority()是String类型,一般用SimpleGrantedAuthority(role)来实例化
        */
        Collection<? extends GrantedAuthority> getAuthorities();
 
 
        /**
        *   返回证明用户身份的证书,一般是用户密码
        */ 
        Object getCredentials();
        
 
        /**
        *   身份验证request额外的细节,如IP地址,和证书序列号
        */ 
        Object getDetails();
 
        
        /**
        *   返回用户身份,一般是继承了UserDetail的Entity
        */ 
        Object getPrincipal();
 
 
 
        /**
        *   如果是true,则表示token已经验证通过了,无需再调用AuthenticationManager进行验证
        */ 
        boolean isAuthenticated();
 
 
 
        /** 
        * 见isAuthenticated()
        */
	  void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

介绍完了Authentication,再来聊聊AuthenticationManager和AuthenticationProvider。它们两个都是提供authenticate方法的接口,不同的是AuthenticationProvider多了一个supports()方法。一般采用AuthenticationProvider进行authenticate验证


public interface AuthenticationManager {
	 /**
	 * 验证传入的authentication信息
         * 验证成功则返回一个包含authorities,并设置isAuthorized=true的完整Authentication
         * 验证失败则抛出异常
	 * @param authentication the authentication request object
	 *
	 * @return a fully authenticated object including credentials
	 *
	 * @throws AuthenticationException if authentication fails
	 */
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}
public interface AuthenticationProvider {
	 /**
	 * 和AuthenticationManager中的authenticate方法一致
	 * @throws AuthenticationException if authentication fails.
	 */
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
 
	/**
	 * 判断AuthenticationProvider是否支持authentication的补全
	 */
	boolean supports(Class<?> authentication);
}

AuthenticationProvider的抽象类AbstractUserDetailsAuthenticationProvider提供了authenticate和(abstract)retrieveUser() 方法。

DaoAuthenticationProvider继承了AbstractUserDetialsAuthenticationProvider,并没有重写authenticate方法,但实现了抽象方法retrieveUser(),通过username从用户自定义UserDetailService(或者系统内置的InMemoryUserDetailsManager)中获取UserDetails,再与authentication进行比对

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException

二、UserDetails,UserDetailsService,UserCache,User

UserDetails提供基本用户信息,如getUserName(),getPassword(),List  getAuthorities()等。

但一般不会直接使用UserDetails存用户信息,而是包装后放到Authentication中的principle和authorities中。

一般自定义用户实体类需要继承UserDetails。

public interface UserDetails extends Serializable {
	// 角色
	Collection<? extends GrantedAuthority> getAuthorities();
 
	
	String getPassword();
 
	
	String getUsername();
 
 
	boolean isAccountNonExpired();
 
 
	boolean isAccountNonLocked();
 
 
	boolean isCredentialsNonExpired();
 
	
	boolean isEnabled();
}

UserDetailsService就简单了,只有一个方法loadUserByUsername,通过用户名找用户信息UserDetails。

用户可以编写实体类继承该接口,实现loadUserByUsername(从数据库里查询),也可以利用auth.inMemoryAuthentication()创建InMemoryUserDetailsManager。

public interface UserDetailsService {
	/**
	 * Locates the user based on the username. In the actual implementation, the search
	 * may possibly be case sensitive, or case insensitive depending on how the
	 * implementation instance is configured. In this case, the <code>UserDetails</code>
	 * object that comes back may have a username that is of a different case than what
	 * was actually requested..
	 *
	 * @param username the username identifying the user whose data is required.
	 *
	 * @return a fully populated user record (never <code>null</code>)
	 *
	 * @throws UsernameNotFoundException if the user could not be found or the user has no
	 * GrantedAuthority
	 */
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserCache

可以代替UserDetailsService获得用户基本信息UserDetail,如果有缓存则不用每次都执行loadUserByUsername,只需从UserCache中调用UserDetail getUserFromCache(String Username)方法即可。如果没有缓存就执行loadUserByUsername,并把获取的UserDetail存入缓存中。

User

继承了UserDetails和CredentialsContainer两个接口,常用于UserDetailsService中返回loadUserByUsername()方法的结果(代替自己定义的User实体),但注意的是,必须每次返回的都是新建的User,因为它不是immutable。CredentialsContainer用于清除敏感信息。

 User包含属性和方法如下,

 private String password;
	private final String username;
	private final Set<GrantedAuthority> authorities;
	private final boolean accountNonExpired;
	private final boolean accountNonLocked;
	private final boolean credentialsNonExpired;
	private final boolean enabled;
 
        //  继承自CredentialsContainer,继承该方法的类如Authentication需要把敏感信息credential给去除掉
        public void eraseCredentials() {
		password = null;
	}

三、SecurityContext,SecurityContextHolder

SecurityContext是存储Authentication的容器,结构如下。登录时用户名密码验证成功后获得Authentication,将其存入SecurityContext中,再将SecurityContext存到session里面,如俄罗斯套娃一般。

public interface SecurityContext extends Serializable {
 
	Authentication getAuthentication();
 
	void setAuthentication(Authentication authentication);
}

SecurityContextHolder专门用来操作SecurityContext,有static方法setSecurityContext()和getSecurityContext()等。

它的生命周期是一次request,FilterChain中的SecurityContextPersistenceFilter一开始会通过session中的属性“SPRING_SECURITY_CONTEXT”获取securityContext,并存入SecurityContextHolder中。

如果session中没有属性”SPRING_SECURITY_CONTEXT”,那么会进入UsernamePasswordAuthenticationFilter进行登录验证,验证成功会按照authentication ——>>>securityContext——>>>session的顺序进行存放,一次请求最后会执行SecurityContextHolder.clearContext()操作。

如果是注销用户,则会在FilterChain进行到LogoutFilter时,执行注销操作,大意是先执行session.invalidate(),再执行SecurityContextHolder.clearContext(),最后跳转到指定的页面(一般是登录页面,可在WebSecurityConfigurerAdapter中配置)

四、WebSecurityConfigurerAdapter

这个东西牛掰了,它是spring security的默认http配置。我们可以用它的实现类配合@EnableWebSecurity完成下面功能,也是我们唯一需要手动配置的地方。

要求用户在进入你的应用的任何URL之前都进行验证

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .
                .authorizeRequests()
                .anyRequest().authenticated();//所有请求必须登陆后访问
}

创建一个用户名是“user”,密码是“password”,角色是“ROLE_USER”的用户 

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // inMemoryAuthentication()对应的UserDetailsService是inMemoryUserDetailsManager
 
                // enable in memory based authentication with a user named
                // "user" and "admin"
                .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
    }

启用HTTP Basic和基于表单的验证

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()//所有请求必须登陆后访问
                // basic验证
                .and().httpBasic()
                // 表单验证
                .and()
                    .formLogin()
                    .loginPage("/login")
                    .defaultSuccessUrl("/main")
                    .failureUrl("/login?error")
                    .permitAll()//登录界面,错误界面可以直接访问
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login")
                .permitAll();//注销请求可直接访问
    }

Spring Security将会自动生成一个登陆页面和登出成功页面

WebSecurityConfigurerAdapter的核心方法有以下三个

  1. configure(HttpSecurtiy http)
  2. configure(WebSecurity web)      
  3. configure(AuthenticationManagerBuilder auth)

// order顺序是100,数值越小越先执行
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {
 
     /* 可以在inMemoryAuthentication中设置两个用户,包含对应的密码和权限
     *   auth  	 
     *.inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
     *  .withUser("admin").password("password").roles("USER", "ADMIN");
     *
     *  也可以设置UserDetailsService,passwordEncoder
     *  auth.userDetailsService(new myUserDetailsService())
     *    .passwordEncoder(new BCryptPasswordEncoder());;
     */
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		this.disableLocalConfigureAuthenticationBldr = true;
    }
 
 
    /**
	 * Override this method to configure {@link WebSecurity}. For example, if you wish to
	 * ignore certain requests.
	 */
	public void configure(WebSecurity web) throws Exception {
          web.ignoring().antMatchers("/hello");   排除路径拦截
	}
 
        
        // 可以启用httpBasic和表单认证,哪些endpoint需要认证等
        protected void configure(HttpSecurity http) throws Exception {
		http
             //http.authorizeRequests()方法有多个子节点,每个macher按照他们的声明顺序执行     
             .authorizeRequests()      
 
             //我们指定任何用户都可以访问多个URL的模式。
             //任何用户都可以访问以"/resources/","/signup", 或者 "/about"开头的URL。                                                     
            .antMatchers("/resources/**", "/signup", "/about").permitAll()     
 
             //以 "/admin/" 开头的URL只能让拥有 "ROLE_ADMIN"角色的用户访问。
             //请注意我们使用 hasRole 方法,没有使用 "ROLE_" 前缀。               
            .antMatchers("/admin/**").hasRole("ADMIN")               
 
             //任何以"/db/" 开头的URL需要同时具有 "ROLE_ADMIN" 和 "ROLE_DBA"权限的用户才可以访问。
             //和上面一样我们的 hasRole 方法也没有使用 "ROLE_" 前缀。              
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")       
 
             //任何以"/db/" 开头的URL只需要拥有 "ROLE_ADMIN" 和 "ROLE_DBA"其中一个权限的用户才可以访问。
            //和上面一样我们的 hasRole 方法也没有使用 "ROLE_" 前缀。          
            .antMatchers("/db/**").hasAnyRole("ADMIN", "DBA")    
 
             //尚未匹配的任何URL都要求用户进行身份验证
            .anyRequest().authenticated()                                                
            .and()
        // ...
        .formLogin();
 
	}
 
}

总结

  1. Authentication      AuthenticationProvider       ProviderManager
  2. UserDetails     UserDetailsService      DaoAuthenticationProvider
  3. SecurityContext     SecurityContextHolder
  4. WebSecurityConfiguererApdater

再反过来看开篇的Spring Security验证过程,是不是有了更全面的认识呢

  1. 用户编写WebSecurityConfigurerApdater的继承类,配置HttpSecurity,包括formLogin,antMatcher,hasRole等等。
  2. 项目启动自动装配FilterChainProxy,根据HttpSecurity的配置来初始化一组filters(其实对应不同的URI,都有对应的一组Filters进行身份认证)。
  3. 用户输入账号、密码点击登录,FilterChain中的UsernamePasswordAuthenticationFilter获取request中的用户名、密码,验证身份信息
  4. doFilter()过程中会执行ProviderManager.authenticate(),即遍历所有AuthenticationProvider执行authenticate()方法。
  5. authenticate()方法中会调用userDetailService,用户自定义类继承UserDetailService,并重写其中的方法loadUserByUsername(),从数据库中获取用户信息进行比对
  6. 比对成功后将用户信息和角色信息整合成Authentication,并存入SecurityContext中,同时将SecurityContext也存入session中,跳转到主页面。
  7. 比对失败,SecurityContext中没有Authentication,FilterChain进行到最后一步FilterSecurityInterceptor,判断用户角色是否能访问request中的访问地址即资源。如果不行则报错跳转到指定页面;如果成功则进入request调用的资源。
  8. 注销操作由LogoutFilter执行,执行session.invalidate()和SecurityContextHolder.clearContext()。

相关笔记:

SpringSecurity—捋一遍登录流程( 从源码出发 )

SpringSecurity—WebSecurityConfigurerAdapter(自定义配置入口)

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594024浏览

随机文章
SpringSecurity—AuthenticationManagerBuilder (认证管理器分析)
5年前
SpringBoot—自动配置原理
5年前
SpringBoot—操作 Redis
5年前
Netty—NIO基础
2年前
Kotlin-内置类型—基本类型(二)
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594024 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付