SpringSecurity你的项目城卫

SpringSecurity到底是什么?

  • 1.Security从字面上意思我就能看出它是一个安全框架,提供给我们自主验证的一套流程。

流程分析:

  • UsernamePasswordAuthenticationFilter未验证—AuthticationManager—AuthenticationProvider—UserDetailsService—UserDetails—Authentication(已认证)—SecurityContext—SecurityContextHolder—SecurityContextPersistenceFilter。

  • 在其中在执行的工程中,实际上是由很多Filter组成的队列。

  • 由于我账号不是vip不能画流程图,这里就不展示图给大家看了,在我的公众号里面有。

  • 1.首先它和大多数框架一样需要配置(例如Mybatis),在这里,我们需要一个自定义的配置文件,当然,你如果不进行配置的话,程序在启动的时候,会走自己的默认路径,一但请求,直接被拦截。我们的配置文件必须要实现WebSecurityConfigurerAdapter接口,这个接口给我们定义了许多的认证方法,配置入下。

  • 在项目中还需要添加我们的依赖包

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version> 2.1.4.RELEASE</version>
        </dependency>
  • 配置文件,一定要继承WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
    *@Description:ContentUpdate  authenticationManagerBuilder 添加用戶----角色
    *@Date:2019/7/22
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("admin")).roles("ADMIN","USER")

                .and()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user")
                .password(new BCryptPasswordEncoder().encode("user")).roles("USER");
    }
    /**
    *@Description:ContentUpdate httpSecurity 添加角色----访问路径
    *@Date:2019/7/22
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/hello/**").hasRole("ADMIN")
                .mvcMatchers("/other/**").hasRole("USER")
                .mvcMatchers("/order/**").permitAll()
                .mvcMatchers("/user/**").permitAll()
                .mvcMatchers("/querInfo/**").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .httpBasic();
    }
}

  • 然后我们写一个Controller进行测试一下
@RestController
@RequestMapping("/hello")
public class SecurityDemo {
    @GetMapping("/security")
    public String loginSecurity(){
        return "Hello World!";
    }
}
  • 3.然后启动项目项目,输入请求路径,然后Security框架自动给你生成一个登陆页面,输入用户名admin,密码admin就可以看到打印结果Hello World!了,现在你已经初步认识了这个安全框架。

处理流程:

  • 1.前端进行请求时,首先是我们的UsernamePasswordAuthenticationFilter进行拦截我们的请求,里面有一个attemptAuthentication方法 封装我们请求数据,一般是是用户名与密码,具体代码如下。
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            //这里就是我们封装的数据,把用户名、密码存放在一个UsernamePasswordAuthenticationToken对象里面,保存起来,并调用authenticate方法进行验证。
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  • 2.点击进入AuthenticationManager接口,你会看到这里有一个authenticate方法,用来验证用户名和密码。

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

  • 3.AuthenticationManager接口实现主要是由ProviderManager类实现的,它重写了里面的authenticate验证方法。
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var7 = this.getProviders().iterator();

        while(var7.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var7.next();
            //判断是否为我们需要验证的数据类型。就是我们自己封装的
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                //1.实际上,是AuthenticationProvider的authenticate方法对用户信息进行验证
                    result = provider.authenticate(authentication);
                    if (result != null) {
                    //验证通过,进行数据复制
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (InternalAuthenticationServiceException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = parentResult = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var10) {
                ;
            } catch (AuthenticationException var11) {
                lastException = var11;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("P**想要知道更多技术,请关注我的紫晶公众号:East_Amethyst Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            this.prepareException((AuthenticationException)lastException, authentication);
            throw lastException;
        }
    }
  • 4可以看到provider.supports(toTest)是验证我们封装的数据类型是否为authention类型的,通过AuthenticationProvider这个类的authenticate,我们获取用户的验证结果,是通过还是不通过,点进这个方法进行查看,看到这是authenticationProvider接口提供的一个方法,,

public interface AuthenticationProvider {
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);
}

这个接口由AbstractUserDetailsAuthenticationProvider类进行实现,重写了authentication方法,有时候没有进行重写,则是由AbstractUserDetailsAuthenticationProvider这个类的子类DaoAuthenticationProvider进行重写,具体看你使用的jdk版本,我的是tractUserDetailsAuthenticationProvider进行重写的,源码如下:

  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
            //从数据库获取数据
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            //验证用户名密码是否正确
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        //把验证的信息保存在封装数据的result.setDetails中
        //一般这个封装的类UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken,里面有个Details属性用来保存验证前 后数据。
        //实现Authentication
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

  • 5.查看我们的this.retrieveUser方法,我在代码中都进行了说明,你会发现调用了loadUserByUsername方法从数据库获取数据,调用additionalAuthenticationChecks方法对我们的密码进行验证,这个方法我们可以重写。
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
        //调用loadUserByUsername方法
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }
  • 6.这个loadUserByUsername方法我们可以看到是在UserDatailsService这个里面实现的。
public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

  • 这个类有好几个实现类,当然我们也可以自定义实现类,从我们自己的数据库中回去数据,进行返回。
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

  • additionalAuthenticationChecks方法对密码进行验证,验证我们前端输入的数据是否为我们数据库里,如果是一致,就把我们的验证信息保证在AbstractAuthenticationToken类的Details属性中,验证通过,后台返回数据到前端。
  • 当我们把用户信息保存了,在以后要用的时候,有三种方式获取用户验证的信息,实现验证信息不多个请求中共享。
  • ==通过SecurityContextHolder.getContext().getAuthentication();==
  • ==方法参数(Authentication authtication)==
  • ==通过在方法参数里注解获取(@AuthticationPrincipal UserDetails user)==
  • 6.这就是SpringSecurity的验证流程源码,如果你想要了解自定义的验证流程,你可以关注我的紫晶公众号:East_Amethyst,或者有时间了再来写在这里,请关注我哦。里面有很多免费的东西,都是写干货。

SpringSecurity应用场景

  • 1.我们可以用这个框架来拦截我们前端的请求,封装请求数据,对用户进行认证功能。
  • 2.不同用户请求,我们设置不同的权限,当然角色-权限-操作是存于数据里里,不同的权限,我们设置不同的操作,返回不同的数据给前端。
  • 3.现在请求访问,允许哪些请求要验证,哪些请求不用验证之类的,相当于拦截器的功能。

为什么要用SpringSecurity

  • 使用SpringSecurity安全验证可以自定义我们的认证,验证逻辑,对不同的请求做不同的逻辑处理,保存用户验证信息等,当然了学习难度开始还是比较大的,不容易理解。

分析问题,提问

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,096评论 1 32
  • Spring Security 是一个基于 Spring AOP 和 Servlet 过滤器的安全框架,它提供了安...
    聪明的奇瑞阅读 17,106评论 3 38
  • 一、简历准备 1、个人技能 (1)自定义控件、UI设计、常用动画特效 自定义控件 ①为什么要自定义控件? Andr...
    lucas777阅读 5,200评论 2 54
  • 目标百分百! 当第一次听到这句话的时候,自己不太认可,一直觉得成功=目标+方法,他们应该平分秋色,但是转念之间,回...
    迷糊糊阅读 79评论 0 0
  • 经过这么多年的努力 理想终成泡影 而如今 启程的路就在脚下 来吧 风雨也罢 雷暴也罢 在路上 都随它 因为我 不曾...
    灵魂行走201255阅读 191评论 0 0