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 { @Override
protected 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 checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (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 {
@Override
public 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
@Override
protected 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 {
@Override
public 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)

三、处理密码加密解密
以下两种方式,都可以使用到加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
或:
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
spring security 学习一的更多相关文章
- Spring security 学习 (自助者,天助之!)
自己努力,何必要强颜欢笑的求助别人呢? 手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限: a:pom.xml配置jar包 b:cr ...
- SpringBoot + Spring Security 学习笔记(五)实现短信验证码+登录功能
在 Spring Security 中基于表单的认证模式,默认就是密码帐号登录认证,那么对于短信验证码+登录的方式,Spring Security 没有现成的接口可以使用,所以需要自己的封装一个类似的 ...
- SpringBoot + Spring Security 学习笔记(三)实现图片验证码认证
整体实现逻辑 前端在登录页面时,自动从后台获取最新的验证码图片 服务器接收获取生成验证码请求,生成验证码和对应的图片,图片响应回前端,验证码保存一份到服务器的 session 中 前端用户登录时携带当 ...
- [转]Spring Security学习总结二
原文链接: http://www.blogjava.net/redhatlinux/archive/2008/08/20/223148.html http://www.blogjava.net/red ...
- [转]Spring Security学习总结一
[总结-含源码]Spring Security学习总结一(补命名空间配置) Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27) 编辑 收藏 所属分 ...
- spring security 学习资料
spring security 学习资料 网址 Spring Security 文档参考手册中文版 https://springcloud.cc/spring-security.html
- Spring Security学习笔记
Spring Web Security是Java web开发领域的一个认证(Authentication)/授权(Authorisation)框架,基于Servlet技术,更确切的说是基于Servle ...
- SpringBoot + Spring Security 学习笔记(二)安全认证流程源码详解
用户认证流程 UsernamePasswordAuthenticationFilter 我们直接来看UsernamePasswordAuthenticationFilter类, public clas ...
- spring security学习总结
这几天一直在学习spring security的相关知识.逛各大论坛,看相关api与教学视频,获益良多! 简介 Spring Security是为基于Spring的企业应用系统提供声明式的安全访问控制 ...
随机推荐
- codeforces510D
Fox And Jumping CodeForces - 510D Fox Ciel is playing a game. In this game there is an infinite long ...
- Oracle篇 之 子查询
子查询:先执行内部再外部 Select last_name,salary,dept_id From s_emp Where dept_id in ( Select dept_id From s_emp ...
- jQuery中$.each()方法(遍历)
$.each()是对数组,json和dom结构等的遍历,说一下他的使用方法吧. 1.遍历一维数组 var arr1=['aa','bb','cc','dd']; $.each(arr1,functio ...
- linux Java项目CPU内存占用高故障排查
linux Java项目CPU内存占用高故障排查 top -Hp 进程号 显示进程中每个线程信息,配合jstack定位java线程运行情况 # 线程详情 jstack 线程PID # 查看堆内存中的对 ...
- python3.x执行post请求时报错“POST data should be bytes or an iterable of bytes...”的解决方法
使用python3.5.1执行post请求时,一直报错"POST data should be bytes or an iterable of bytes. It cannot be of ...
- Linux基础知识梳理
Linux基础知识梳理 Linux内核最初只是由芬兰人林纳斯?托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的.Linux是一套免费使用和自由传播的类Unix操作系统,是 ...
- 实现select联动效果,数据从后台获取
效果如下: 当type值选择完后,amount值会自动相应填入. 1. 从后台获取数据,为一个数组,里面包含多个对象. <select id="scholarshipTypeSelec ...
- python学习07
函数中的模块及包管理1)1.模块查找的顺序:运行代码时当前目录 -> PYTHONPATH ->系统环境变量PATH设置的路径2.导入模块的书写规范:内置模块-------第三方模块--- ...
- 20155324《网络对抗》Exp2 后门原理与实践
20155324<网络对抗>Exp2 后门原理与实践 20155324<网络对抗>Exp2 后门原理与实践 常用后门工具实践 Windows获得Linux Shell 在Win ...
- 在web项目启动时,执行某个方法
在web项目中有很多时候需要在项目启动时就执行一些方法,而且只需要执行一次,比如:加载解析自定义的配置文件.初始化数据库信息等等,在项目启动时就直接执行一些方法,可以减少很多繁琐的操作. 在工作中遇到 ...