springsecurity讲解1

SpringSecurity是一个权限验证的安装框架,有身份验证(用户名密码)和用户授权(权限)等功能.

快速上手

SpringSecurity在整合springboot的时候非常简单,只在maven文件中引入jar即可
pom.xml

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

在日志中有密码,在访问restfull接口的时候,需要输入用户名和密码,
密码是在日志中提示的,用户名是user

Using generated security password: afdda172-b982-40aa-9552-18560c8e8ecc
进阶

快速上手是springsecurity默认的,如何定制化呢
需要实现WebSecurityConfigurerAdapter,首先实现类要用@Configuration
和@EnableWebSecurity标注
重写两个方法configure(HttpSecurity http)配置规则和configure(AuthenticationManagerBuilder auth)身份认证,从内存或者数据库中获取用户信息,configure(WebSecurity web)可以规定忽略哪些路径

@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                .antMatchers("/hello").permitAll() // 对于hello路径放行
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
        ;  //浏览器以form表单形式

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        // 用户信息存储在内存中
        auth.inMemoryAuthentication().withUser("user")
            .password(new BCryptPasswordEncoder().encode("1234")).authorities("ADMIN");
    }

    @Override
    public void configure(WebSecurity web) throws Exception{
//忽略/world路径
        web.ignoring().antMatchers("/world");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        // 官网建议的加密方式,相同的密码,每次加密都不一样,安全性更好一点
        return new BCryptPasswordEncoder();
    }

上面的示例,在内存中生成了用户,使用form表单的方式,放行了 hello和world路径的访问

filter

那springsecurity是如何工作的,就是依托于servlet的filter调用链,加上springsecurity的filter,这个filter中又维护了一个filter链,springsecurity终止自己的调用链后,继续执行servlet的filter
org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter看这个方法就能理解security的filter的大致工作原理,currentPosition为security的filter自己调用链的数量,如果都执行完,就继续执行servlet的filter
springsecurit主要的filter为
--UsernamePasswordAuthenticationFilter 校验用户名密码,主要的作用为验证用户名密码,如果正确则退出springsecurity自己的过滤器链.
--ExceptionTranslationFilter 如果FilterSecurityInterceptor权限不通过,重定向到登入页
--FilterSecurityInterceptor 校验权限等
当然不止这三个,所有filter之间的order为100,目的是为了可以将自定义的filter放到容器中并指定顺序,
一般都是放到UsernamePasswordAuthenticationFilter 前后,加入自己的filter之后,访问都会执行整个调用链,也就都会访问自己添加的filter,所以需要考虑加判断,不满足条件直接跳过,执行下一个filter,获取或者终止调用链,直接跳过当前filter就是执行chain.doFilter(request, response);终止就是不执行
chain.doFilter(request, response);那调用链就结束了.如果要深入了解可以关注
org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter
这里分析UsernamePasswordAuthenticationFilter ,因为我们自定义的filter都是遵循
UsernamePasswordAuthenticationFilter 的模式,比如想做一个手机验证.
UsernamePasswordAuthenticationFilter 是如何工作的,这里就不详细分析每一个细节了,大概分析思路,就可以扩展了.
UsernamePasswordAuthenticationFilter 是AbstractAuthenticationProcessingFilter的子类,在构造方法中初始化了AntPathRequestMatcher


image.png

意思是我只对login路径,方法为post进行拦截,自定义自己的filter的时候,就可以模仿这个构造器
doFilter方法在父类AbstractAuthenticationProcessingFilter中,可以看到调用了
attemptAuthentication方法,

authResult = attemptAuthentication(request, response);

UsernamePasswordAuthenticationFilter 的attemptAuthentication方法的返回值

return this.getAuthenticationManager().authenticate(authRequest);

this.getAuthenticationManager()返回的是ProviderManager,那看一下authenticate
方法中的for循环

for (AuthenticationProvider provider : getProviders())

for循环里关注两个代码,
if (!provider.supports(toTest))和result = provider.authenticate(authentication);
toTest是放进来的Token,这里指的是UsernamePasswordAuthenticationToken,
如果想要自定义,比如手机验证使用自定义mobileAuthenticationToken,
还需要提供自己的provider,因为getProviders()返回的是provider集合,那如何知道使用哪个provider,就是通过if (!provider.supports(toTest))
举例手机验证就能清晰一点
MobileAuthenticationToken 直接复制UsernamePasswordAuthenticationToken代码,改一下类名就可以,
MobileAuthenticationProvider继承AuthenticationProvider,实现两个方法
authenticate(Authentication authentication)和supports(Class<?> authentication)
这两个方法就对应上面说的for循环中的两行代码
authenticate(Authentication authentication)方法中,验证手机验证码是否正确,返回MobileAuthenticationToken 中this.authenticated要复制为true,证明已经验证成功了,这个非常重要,
创建MobileAuthenticationFilter,复制UsernamePasswordAuthenticationFilter就好,简单改一下就可以,构造方法中创建AntPathRequestMatcher,拦截路径/mobile/form,方法POST,那如何将自定义的filter放到springsecurity的调用链呢,最开始说的WebSecurityConfigurerAdapter子类重写的configure(HttpSecurity http)方法中
另一个要考虑的问题就是,登入之后其他路径是怎么被放行的呢,先讲解通过session的方式


image.png

这就是登入成功之后,可以被访问的原因,这是其中一个filter,SecurityContextPersistenceFilter,
通过sessionid从内存中获取MobileAuthenticationToken ,并放到SecurityContextHolder中,是ThreadLocal类型的变量,该线程就可以使用了,会在springsecurity的最后一个filter(FilterSecurityInterceptor),从SecurityContextHolder获取MobileAuthenticationToken,并判断
token中的authenticated是否为true,如果是则证明已经认证过,当然还有判断是否有访问该接口的权限,就不展开了.
说回UsernamePasswordAuthenticationToken,

流程就是先调用父类的AbstractAuthenticationProcessingFilter的doFilter
-->authResult = attemptAuthentication(request, response);
UsernamePasswordAuthenticationFilter#attemptAuthentication
-->this.getAuthenticationManager().authenticate(authRequest);
在for循环中调用
result = provider.authenticate(authentication);
UsernamePasswordAuthenticationFilter在调用attemptAuthentication方法的时候,使用UsernamePasswordAuthenticationToken,在for循环里调用provider的support方法,判断应该使用DaoAuthenticationProvider,provider.authenticate(authentication)方法其实是父类的authenticate方法,authenticate作用是通过调用DaoAuthenticationProvider#retrieveUser方法,获取用户信息,并将用户密码和前端输入的密码比较,如果成功之后,要返回UsernamePasswordAuthenticationToken,并将authenticated要为true,证明已经验证过了,这个在自定义filter的时候非常重要.
filter中还有认证成功处理器和认证失败处理器


image.png

如果是前后端分离的,就response写入信息就好了.
刚提到的DaoAuthenticationProvider#retrieveUser方法,获取用户信息,这也是一个扩展点,这种源码用到了成员变量,基本就是可以扩展的地方,流程就是默认给一个值,也可以程序员手动的赋值.

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

这里说的成员变量,就是 this.getUserDetailsService()的userDetailsService
创建一个类实现UserDetailsService,重写loadUserByUsername方法,该方法从数据库里获取用户信息,并封装为UserDetails返回即可.
流程就是这样,下篇文章实战,对上面说的能有个更好的理解.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容