spring security 学习一

1、配置基本的springboot web项目,加入security5依赖,启动项目

浏览器访问,即可出现一个默认的登录页面

2、什么都没有配置 登录页面哪里来的

一般不知从何入手,就看官方文档里是如何做的,官方的文档和api 是最好最完整的介绍和参考,点击链接查看官方文档地址

(https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-oauth2login),或者通过Google 搜索 spring security,

在结果中点击Spring Security Reference,点击进入页面,然后就可以看到关于Spring Security的文档;

通过查看文档发现,WebSecurityConfigurerAdapter 提供的默认的配置,config(HttpSecurty http)中的formLogin(),这个方法内容如下:

    public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());}

查看formLogin()源码,跳转到HttpSecurity类中,这个方法返回一个FormLoginConfigurer<HttpSercurity>类型的数据。再继续来看看这

个FormLoginConfigurer,在FormLoginConfigurer中有个initDefaultLoginFilter()方法:

    private void initDefaultLoginFilter(H http) {DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = (DefaultLoginPageGeneratingFilter)http.getSharedObject(DefaultLoginPageGeneratingFilter.class);if (loginPageGeneratingFilter != null && !this.isCustomLoginPage()) {loginPageGeneratingFilter.setFormLoginEnabled(true);loginPageGeneratingFilter.setUsernameParameter(this.getUsernameParameter());loginPageGeneratingFilter.setPasswordParameter(this.getPasswordParameter());loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());loginPageGeneratingFilter.setAuthenticationUrl(this.getLoginProcessingUrl());}}

这个方法,初始化一个默认登录页的过滤器,可以看到第一句代码,默认的过滤器是DefaultLoginPageGeneratingFilter,下面是设置一些必要的参数,进入到这个过滤器中:

在描述中可以看到,如果没有配置login页,这个过滤器会被创建,过滤器创建后再浏览器访问的时候回指定doFilter()方法:

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;boolean loginError = isErrorPage(request);boolean logoutSuccess = isLogoutSuccess(request);if (isLoginUrlRequest(request) || loginError || logoutSuccess) {String loginPageHtml = generateLoginPageHtml(request, loginError,logoutSuccess);response.setContentType("text/html;charset=UTF-8");response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);response.getWriter().write(loginPageHtml);return;}chain.doFilter(request, response);}

登录页面的配置是通过generateLoginPageHtml()方法创建的,再来看看这个方法内容:

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,boolean logoutSuccess) {String errorMsg = "Invalid credentials";if (loginError) {HttpSession session = request.getSession(false);if (session != null) {AuthenticationException ex = (AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";}}StringBuilder sb = new StringBuilder();sb.append("<!DOCTYPE html>\n"+ "<html lang=\"en\">\n"+ "  <head>\n"+ "    <meta charset=\"utf-8\">\n"+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"+ "    <meta name=\"description\" content=\"\">\n"+ "    <meta name=\"author\" content=\"\">\n"+ "    <title>Please sign in</title>\n"+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"+ "    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"+ "  </head>\n"+ "  <body>\n"+ "     <div class=\"container\">\n");String contextPath = request.getContextPath();if (this.formLoginEnabled) {sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"+ createError(loginError, errorMsg)+ createLogoutSuccess(logoutSuccess)+ "        <p>\n"+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"+ "          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"+ "        </p>\n"+ "        <p>\n"+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"+ "          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"+ "        </p>\n"+ createRememberMe(this.rememberMeParameter)+ renderHiddenInputs(request)+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"+ "      </form>\n");}if (openIdEnabled) {sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"+ "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"+ createError(loginError, errorMsg)+ createLogoutSuccess(logoutSuccess)+ "        <p>\n"+ "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"+ "          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"+ "        </p>\n"+ createRememberMe(this.openIDrememberMeParameter)+ renderHiddenInputs(request)+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"+ "      </form>\n");}if (oauth2LoginEnabled) {sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");sb.append(createError(loginError, errorMsg));sb.append(createLogoutSuccess(logoutSuccess));sb.append("<table class=\"table table-striped\">\n");for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {sb.append(" <tr><td>");String url = clientAuthenticationUrlToClientName.getKey();sb.append("<a href=\"").append(contextPath).append(url).append("\">");String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());sb.append(clientName);sb.append("</a>");sb.append("</td></tr>\n");}sb.append("</table>\n");}sb.append("</div>\n");sb.append("</body></html>");return sb.toString();}

3、去掉默认的登录页,修改application.yml,添加一下内容(在security5中不在支持以下配置,而是提供一个自定义的WebSecurityConfigurer文件)

The security auto-configuration is no longer customizable. Provide your own WebSecurityConfigurer bean instead.
security:basic:enabled: false

4、自定义WebSecurityConfigurer

以下配置是创建一个最简单的基于form表单认证的security

formLogin():指定认证为form表单

authorizeRequests():授权

anyRequest():任何请求

authenticated():都需要认证

@Configuration
public class CusWebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//指定是表单登录.and().authorizeRequests()//授权.anyRequest()//任何请求.authenticated();//都需要身份认证}
}

5、基本流程

过滤器链有以下:

①UsernamePasswordAuthenticationFilter

  在过滤器容器中判断请求中是否有用户名和密码,如果有用户名和密码就会使用UsernamePasswordAuthenticationFilter这个过滤器,如果没有就会走下一个过滤器

②BasicAuthenticationFilter

  在这个过滤器中回尝试获取请求头中是否有basic开头的Authentication信息,如果有

就会尝试解码,处理完成之后会走下一个filter

③ExceptionTranslationFilter

 这个过滤器的作用是用来捕获下边这个FilterSecurityInterceptor抛出的异常

④FilterSecurityInterceptor

  这个拦截器是过滤器链中的最后一环,在这个里边会判断当前请求能否访问controller,

能否访问是根据securityconfig配置来判断的

即:

6、源码学习

FilterSecurityInterceptor关键源码

在invoke方法中有一个super.beforeInvocation方法,如上图,绿色的过滤器链都是在这个方法中进行处理的

public void invoke(FilterInvocation fi) throws IOException, ServletException {if ((fi.getRequest() != null)&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)&& observeOncePerRequest) {// filter already applied to this request and user wants us to observe// once-per-request handling, so don't re-do security checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// first time this request being called, so perform security checkingif (fi.getRequest() != null && observeOncePerRequest) {fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {super.finallyInvocation(token);}super.afterInvocation(token, null);}}

当访问api:localhost:9999/h  时,请求回走到FIlterSecurityInterceptor中的beforeInvocation处

因为自己的securityConfig的是所有的请求都需要进行认证,因此在执行befoeInvocation的时候会抛出一个异常,也就是进入了ExceptionTranlationFilter中(因为没有经过认证,不能访问api)

在ExceptionTranlationFilter对异常进行处理,也就是把请求重定向到login页面上。

在登录页面填写登录名和密码(user/4ed8dccc-9425-4f92-8b12-0bac0d88793b,密码后台日志会2自动输出),填写完毕后点击登录,又因为使用的是form表单登录,所以会进入到UserNamePasswordAuthenticationFilter中,

在UsernamepasswordAuthenticationFilter中执行完毕后,回再次进入到FilterSecurityInteceptor中的beforeInvocation处,此时执行到该处是不会报错,回向下继续进行。

调用doFilter,也就是进入了自己写的api接口中(controller中)

整个的流程:FilterSecurityInterceptor拦截请求,没有认证,重定向到默认的form认证页面(login),在登录页面输入用户名密码,点击登录后,会进入到UsernamePasswordAuthenticationFilter中(因为使用的form表单认证,如果使用其他认证的话,会进入到其他Filter中),在UsernamePasswordAuthenticationFilter中执行完毕后,回再次进入FIlterSecurityInterceptor中,执行没问题后,最终到controller层中的api处

自定义认证逻辑

一、处理用户信息获取逻辑

在security中用户信息的获取中,提供了一个接口UserDetailService,该接口中只有一个方法loadUserByUsername,返回参数是一个UserDetail

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

在获取用户信息是,只需要关注一点即,获取UserDetail用户信息,之后的认证都是基于此对象的,

当查不到一个username是,会抛出一个UsernameNotFoundException异常,可以进行异常统一拦截

1、新建MyUserDetailsService(数据都是写死的)

(ps:security5好像不能只写一个userdetailservice就行运行,也得配一个PasswordEncoder,即在security配置文件中添加)

@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//根据username查找用户信息,在这,先手动写一个user信息log.info("查找用户信息{}", s);

     //密码在security5中好像得加密  不加密的话会爆粗(不确定)

       BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
      String password = encoder.encode("password");

    //这个user对象使用的是security中的user对象,此对象已经实现了userDetail接口//user前两个参数是进行认证的,第三个参数是当前用户所拥有的权限,security回根据授权代码进行验证return new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
//        AuthorityUtils.commaSeparatedStringToAuthorityList 这个方法是将一个字符一“,” 分割开}
}
//passwordEncoder
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}

添加w完毕后,访问localhost:9999/h api,先回跳转到默认登录页面:

随便输入username和password:会出现bad credentials信息

如果密码输入password(后台user自定义加密后的password):会登录成功,并会执行controller

二、处理用户校验逻辑

1、 用户的校验逻辑主要就是比较密码是否匹配,这一块有security自动匹配(即将user信息放到Userdetail解耦的实现类中即可)

2、账号是否过期、是否被锁定、是否可用,这几个校验都可以重新以下方法(如果没有对应的逻辑,永远返回true即可)

    boolean isAccountNonExpired();//账号没有过期   如果不需要的话,改为true,没有过期boolean isAccountNonLocked(); //账号没有锁定锁定boolean isCredentialsNonExpired();//密码是否过期了boolean isEnabled();//这个可以配到库中

3、自己测试

修改loadUserByUsername方法的返回参数

①accountNonLock设为false

@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//根据username查找用户信息,在这,先手动写一个user信息log.info("查找用户信息{}", s);BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = encoder.encode("password");//user前两个参数是进行认证的,第三个参数是当前用户所拥有的权限,security回根据授权代码进行验证return new User(s, password, true, true, true, false,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
//        AuthorityUtils.commaSeparatedStringToAuthorityList 这个方法是将一个字符一“,” 分割开}
}

修改问之后,重启测试用的项目后,方法api,输入用户密码后,提示已被锁定(即accountNonLock属性被设为false)

三、处理密码加密解密

以下两种方式,都可以使用到加密

    @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}

或:

   @Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}

posted @ 2019-04-22 21:37 巡山小妖N 阅读(...) 评论(...) 编辑 收藏

spring security 学习一相关推荐

  1. spring security 学习三-rememberMe

    spring security 学习三-rememberMe 功能:登录时的"记住我"功能 原理: rememberMeAuthenticationFilter在security过 ...

  2. spring security 学习二

    spring security 学习二 doc:https://docs.spring.io/spring-security/site/docs/ 基于表单的认证(个性化认证流程): 一.自定义登录页 ...

  3. Spring security 学习 (自助者,天助之!)

    自己努力,何必要强颜欢笑的求助别人呢?  手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限:  a:pom.xml配置jar包 b:cr ...

  4. Spring Security学习(二)

    以下配置基于表单登录配置 自定义配置登录页面 @Override protected void configure(HttpSecurity http) throws Exception {http. ...

  5. Spring Security 学习-环境搭建(一)

    首先说明一下基础环境的配置: JDK:1.8.0_144 IDE:STS(spring官方提供的基于eclipse的开发工具) :具体工具请自行搜索下载安装这里不做过多解释 1 搭建Spring Se ...

  6. Spring Security学习总结

    2019独角兽企业重金招聘Python工程师标准>>> 1.Spring Security介绍  一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 ...

  7. Spring Security学习 详细

    Spring Security 是一个提供身份验证.授权和防止常见攻击的框架.凭借对保护命令式和反应式应用程序的一流支持,它是保护基于 Spring 的应用程序的事实标准. Spring Securi ...

  8. SpringBoot + Spring Security 学习笔记(一)自定义基本使用及个性化登录配置

    官方文档参考,5.1.2 中文参考文档,4.1 中文参考文档,4.1 官方文档中文翻译与源码解读 SpringSecurity 核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) ...

  9. spring security——学习笔记(day05)-实现自定义 AuthenticationProvider身份认证-手机号码认证登录

    目录 5.2 自定义 Provider 身份认证 5.2.1 编码思路和疑问 5.2.2 创建用户信息配置类 PhonePasswordAuthenticationToken 5.2.2 修改自定义的 ...

最新文章

  1. Windows Phone 8初学者开发—第22部分:用演示图板创建卷盘的动画
  2. Kafka 2.8.0发布,与ZooKeeper正式分手!
  3. Maven系列学习(二)Maven使用入门
  4. 089-袁佳鹏-实验报告1
  5. C语言 矩阵的几种乘法
  6. 再说 Spring AOP
  7. 心动的本质是什么_《心动的信号3》:在“烟火气”里嗑糖,素人恋爱究竟有多上头?...
  8. django内置服务器
  9. SQL Server的聚集索引和非聚集索引
  10. 两个pv挂一个vg_王者荣耀2020世冠杯小组赛全部结束,TS和AG、QG和E星一个半区
  11. 史上最全Python快速入门教程
  12. 四大服务器系统,四大厂商八路服务器横向对比 谁是王者?
  13. 【Pytorch】rgb转lab颜色空间转换
  14. 盘古开源解析:物联网时代的芯片产业新趋势
  15. 机器学习——支持向量机(SVM)之超平面、间隔与支持向量
  16. 什么是 CSDN ?
  17. GD32F130之GPIO
  18. 一篇对于了解我自己,挖掘我自己,从而成长的文章
  19. 数理统计——参数估计的无偏性、有效性以及一致性(相合性)
  20. 大容量U盘制作成WinPE启动盘经验

热门文章

  1. B-Tree和B+tree
  2. 开发web前端_移动前端开发和web前端开发的区别?
  3. hibernate mysql 关联查询_Hibernate关联映射及高级查询
  4. 【流媒体服务器的搭建】2. 源码编译安装ffmpeg
  5. java更新无法正常安装_Java无法安装
  6. mysql分页查询limit_MySQL查询语句(where,group by,having,order by,limit)
  7. mysql枫叶_mysql总结
  8. mysql join 组合索引_详解MySQL两表关联的连接表创建单列索引还是组合索引最优...
  9. Apache Flink 零基础入门(十四)Flink 分布式缓存
  10. 批量处理---提高处理速度