学习之路比较遥远,需要一点一点更新。
SpringSecurity的详细介绍就不说了,度娘会告诉你一切。但还是要多说一句:主要就是认证和授权。

光速搭建

其实吧就是创建项目勾一勾就行了,正常搭建一个springboot项目

这里web的勾一勾

这里security的勾一勾,完事!

我们启动项目并访问,会发现进入了security的内置界面:

用户名默认user 密码可以在控制台找到。

自写一下

其实上方的这种形式并不满足我们自己去做事情,你想搭建一个自己的登陆界面,五颜六色的那种,看着就要中病毒的花花的界面,唬唬人,这咋办呢?

那先自定义个鬼屎简单界面吧
定义一个登录界面login.html,注意input标签内的type不要用错了

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="/login" method="post">用户名:<input type="text" name="username"/><br/>密码:<input type="password" name="password"/><br/><input type="submit" value="登录">
</form></body>
</html>

再来一个首页吧,登录成功的页面indexTest.html(这里讲述一个细节:当你命名为index.html时,一旦通过了登录验证会自动跳转到index.html,所以学习过程中,为了深入了解自定义,命名就换一个,不要用index.html命名

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
登录成功
</body>
</html>

定义好了,但是我不会用,咋办。咋能让security认识咱们的页面,用咱们的页面去做认证呢?
咱们先来分析一波

先认识几个Security的走向吧

正常来说,我们是从数据查用户的情况对吧?查用户名,有木有这个用户?然后匹配密码,密码输入的对不对?
我们先看security怎么做:

1.从UserDetailsService开始

public interface UserDetailsService {//参数String var1,就是username 我们输入的用户名//这里就是根据用户名去查信息,返回一个UserDetails对象UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

此时我们需要进一步去看看UserDetails有什么?

public interface UserDetails extends Serializable {//权限Collection<? extends GrantedAuthority> getAuthorities();//密码String getPassword();//用户名String getUsername();//是否未过期boolean isAccountNonExpired();//是否未锁定boolean isAccountNonLocked();//用户认证(这里是指密码)是否未超期boolean isCredentialsNonExpired();//用户是否可用,如果被禁用不能登陆的boolean isEnabled();
}

那么这个接口肯定不是我们要用的东西,我们要用的是他的实现类。我们看他的其中一个实现类——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;//两个构造方法public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {//这里调用的就是下方的构造方法this(username, password, true, true, true, true, authorities);}public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");this.username = username;this.password = password;this.enabled = enabled;this.accountNonExpired = accountNonExpired;this.credentialsNonExpired = credentialsNonExpired;this.accountNonLocked = accountNonLocked;this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));}

判断用户名是否为空,如果为空抛异常;不为空就赋值
实际的登录逻辑大致是这样的:
找UserDetailsService 中的loadUserByUsername方法。根据用户名查数据库看是否存在,如果存在了就比较一下密码,然后匹配正确,就构造一个User对象,赋上相应的用户名、密码、权限等等值。匹配不对就登录失败喽

2.认证密码

public interface PasswordEncoder {//参数CharSequence var1,是指明文密码,方法就是对其加密//这里它推荐使用“SHA-1”或者“哈希使用8位或者随机延续”的方式加密。String encode(CharSequence var1);//匹配,CharSequence var1 明文密码,String var2加密好的密码,进行匹配boolean matches(CharSequence var1, String var2);//对于加密过的密码进一步加密,默认返回falsedefault boolean upgradeEncoding(String encodedPassword) {return false;}
}

这里官方推荐使用其实现类——BCryptPasswordEncoder来进行加密和解密
这里有一个神奇的东西:
相同的明文通过BCryptPasswordEncoder加密的密文每次都是不一样的!
例如:对“123”进行加密,encode()后返回的String 是不同的。也就是你加密很多次都是不一样的密文。但是用这些不同的密文去进行解密,调用matches方法,传入“123”和不同的密文,结果都是返回true。
先上源码

所以是为啥相同明文加密结果密文都不一样,但解密又可以很ok呢?
1、首先聊加密:
他先获取一个随机数salt,然后根据salt通过某种规则获取real_salt,同时real_salt也是参与了加密。

随机数salt + "123"  进行加密 ——> “MMM”
本质,其实salt也并非随便随机,而是有一定规则,是salt+realsalt+"123"进行加密得到密文"MMM"
截图的源码中看数字6上一行代码的rounds是与salt直接相关的而数字6下一行代码的saltb是与realsalt直接相关的
后续的源码会用rounds+saltb内容进行加密

2、其次是解密:
原理是对传入的明文"123"和盐(这里源码中,这个matches方法中先加密的盐不再是随机数,用的就是数据库查出的密文“MMM”)进行加密后生成"xxx",再和数据库存储的密文“MMM”进行匹配。

查数据库拿到密文“MMM”
“123” + 盐(值为“MMM”) 进行加密 ——> “xxx”
再用“xxx” 和 “MMM”进行匹配,不是匹配值,是匹配他们的哈希值,相等就是匹配成功

总结
总结
总结
重要说三遍
所以,加密的过程中
第一次加密:由于用到了随机数salt(具有一定规则的随机数),用不确定值salt去保证得到的密码每次都是不一样的。同时又会根据salt的生成规范,得到一个固定的realsalt(解密要用),然后把salt和realsalt一起融入到密码中进行加密,生成密码。
第二次以及后续加密也会由于salt的不通,得到的密码不同。

解密:把传入的明文值(“如123”)进行加密,然后用数据库查到的密码密文作为salt,由于salt的规则,可以根据生成的密文按规则获取到realsalt,然后把传入的明文+salt一同加密后,得到的新密文与数据库密文进行哈希匹配。

简单自定义1开始

上述已经定义了两个简单页面——indexTest.html和login.html页面。
那么我们要进行自定义的效果:

1.认证前:进入我们自定义的login.html页面去认证,而非security默认登录页面
2.认证通过后:能进入其他页面,比如indexTest.html
3.认证失败后:跳转一个失败页面

好了,小目标明确。那我们要新增一个loginError.html页面,认证失败用。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
登录失败
<a href="/login.html">请重新登录</a>
</body>
</html>

下面开始走后端代码

1.实现UserDetailsService——自定义用户认证时使用

这里有一个困扰本人的问题,暂时未得到解决
问题不加@Service,就走不到自定义的UserDetailServiceImpl中,那么security怎么做的?怎么识别到注入spring就走自定义的逻辑,不注入spring就走原来的默认逻辑。
但可以确定的是。不加@Service注入spring中,就无法走到自定义的逻辑中
下面正式开始:
我们自定义一个类UserDetailsServiceImpl实现UserDetailsService接口
注意:下方的UserData 是由于我懒得链接数据库,自制的一个伪数据。userEnity是用户实体

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String userName)throws UsernameNotFoundException {UserEnity userEnity = new UserEnity();//1.根据用户名查用户存在性userEnity = getUserData(userName, userEnity);if (userEnity == null) {throw new UsernameNotFoundException("用户不存在");}//2.比较密码(注册时已经加密,如果成功返回UserDetails),返回security的user类return new User(userName, userEnity.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin.normal"));}private UserEnity getUserData(String userName, UserEnity userEnity) {UserData userData = new UserData();List<UserEnity> userList = userData.getUserList();for (UserEnity userEnityTemp : userList) {if (userEnityTemp.getUserName().equals(userName)) {//数据赋值userEnity = userEnityTemp;return userEnity;}}return null;}
}
//下方为伪数据代码
@Data
public class UserData {private List<UserEnity> userList = new ArrayList<>();public UserData() {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();UserEnity userEnity = new UserEnity();userEnity.setUserName("admin");userEnity.setPassword(bCryptPasswordEncoder.encode("admin123"));this.userList.add(userEnity);userEnity = new UserEnity();userEnity.setUserName("xiaoming");userEnity.setPassword(bCryptPasswordEncoder.encode("xiaoming123"));this.userList.add(userEnity);userEnity = new UserEnity();userEnity.setUserName("boss");userEnity.setPassword(bCryptPasswordEncoder.encode("boss123"));this.userList.add(userEnity);}
}
//下方为用户实体
@Data
public class UserEnity {private String userName;private String password;
}

2.自定义Security配置类——拦截或放行以及跳转页面用

那么我们开始定义这个配置类了
如下代码:
重点1:@Configuration
重点2:extends WebSecurityConfigurerAdapter
重点3:重写方法configure(HttpSecurity http)这个方法,有很多同名,看清选择
重点4:注释内容要读一下!!!!!

@Configuration
public class SecurityPasswordConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//自定义登录页面.loginPage("/login.html")//自定义登录接口路径,和自定义页面login.html的action=路径要一致//.loginProcessingUrl("/user/login")这个有必要解释一下。他其实就是抓取页面表单提交的路径去交给springSecurity做认证//比如login.html页面的信息表单action="/a"提交,那么这里就要配置/a,抓取/a请求信息去交给SpringSecurity做认证.loginProcessingUrl("/login")//这个successForwardUrl直接跳转页面的话,本质将是一个GET请求,调用会报405错。// 改为跳转路径,则本质为post请求// .successForwardUrl("/indexTest.html") 改为/toIndex.successForwardUrl("/toIndex")//这里就顾名思义了,跳转失败的页面.failureForwardUrl("/loginError");//授权,如果不授权就可以随便进入,授权成功以后,页面不会拦截/*这里需要讲述一个逻辑:1.登录login以后,跳转到成功界面,这个indexTest.html不需要放行,因为已经登录了2.错误页面loginError.html需要放行,因为是登录前的操作*/http.authorizeRequests()//放行,不需要认证的页面.antMatchers("/login.html").permitAll().antMatchers("/loginError.html").permitAll().anyRequest().authenticated();//关闭防火墙,csrf叫跨站请求伪造http.csrf().disable();}@Beanpublic PasswordEncoder getPasswordRule() {return new BCryptPasswordEncoder();}}

此时已经可以启动一波测测看看了!

简单自定义2开始

接着简单自定义1,我们进一步探讨一些东西。
首先从login.html开始

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="/login" method="post">用户名:<input type="text" name="username"/><br/>密码:<input type="password" name="password"/><br/><input type="submit" value="登录">
</form></body>
</html>

username这个参数会传递到哪里?security是怎么去获取这个东西的。
源码看起来费劲可以先看下边的思路对着看源码
上源码对象UsernamePasswordAuthenticationFilter.class:

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");private String usernameParameter = "username";private String passwordParameter = "password";private boolean postOnly = true;public UsernamePasswordAuthenticationFilter() {super(DEFAULT_ANT_PATH_REQUEST_MATCHER);}public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);}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);username = username != null ? username : "";username = username.trim();String password = this.obtainPassword(request);password = password != null ? password : "";UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}@Nullableprotected String obtainPassword(HttpServletRequest request) {return request.getParameter(this.passwordParameter);}@Nullableprotected String obtainUsername(HttpServletRequest request) {return request.getParameter(this.usernameParameter);}
//后续代码省略...
}

思路:
1.源码中首先定义了属性:

private String usernameParameter = "username";
private String passwordParameter = "password";

2.认证逻辑的方法

attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
这个方法中是先判定请求类型是不是POST,不是就报错
然后调用 obtainUsername()方法,也就是获取定义的属性usernameParameter 的值
获取usernameParameter 干嘛?作为key从request中取value

下面问题来了!我想改一下这个参数名称,不想用username,我想驼峰命名或者叫godName(客户就是上帝)我页面传参用想用godName

这怎么办,他怎么识别?别慌,改!
1.改login.html

//先把用户名的name值改了
<form action="/login" method="post">用户名:<input type="text" name="godName"/><br/>密码:<input type="password" name="password"/><br/><input type="submit" value="登录">
</form>

然后改哪里?
2.改config类

@Configuration
public class SecurityPasswordConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/login.html")//这里就是改造点,加个 .usernameParameter("godName").usernameParameter("godName").loginProcessingUrl("/login").successForwardUrl("/toIndex").failureForwardUrl("/loginError");http.authorizeRequests().antMatchers("/login.html").permitAll().antMatchers("/loginError.html").permitAll().anyRequest().authenticated();http.csrf().disable();}@Beanpublic PasswordEncoder getPasswordRule() {return new BCryptPasswordEncoder();}}

上述两处改完即可
这样子就实现了username参数名称的自定义。password也是一样的改造2点,可以照葫芦画瓢,自行进行一定的参悟和探索~

那我看源码是写死的username

 private String usernameParameter = "username";

咋后台这里改了就可以呢??其实是这样的:
程序启动过程中
1.usernameParameter 属性的值会被赋值为username
2.调用配置类SecurityPasswordConfig 中新增的.usernameParameter(“godName”)方法
点进去可以看到下方美景:

public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).setUsernameParameter(usernameParameter);return this;
}

可以看到.usernameParameter(“godName”)其实是调用了UsernamePasswordAuthenticationFilter中的setUsernameParameter,也就是把属性usernameParameter的值替换为了“godName”,后边再从request中取value用的key将会是“godName”

SpringSecurity的旅途(喜欢的话,可以点个赞哦~)相关推荐

  1. PTA-L1-034微博上有个“点赞”功能,你可以为你喜欢的博文点个赞表示支持。每篇博文都有一些刻画其特性的标签,而你点赞的博文的类型,也间接刻画了你的特性。本题就要求你写个程序,通过统计一个人点赞的

    题目来源: PTA-L1-034 ***题目:**加粗样式 微博上有个"点赞"功能,你可以为你喜欢的博文点个赞表示支持.每篇博文都有一些刻画其特性的标签,而你点赞的博文的类型,也间 ...

  2. 【探花交友DAY 06】圈子中的互动功能(点赞、评论、喜欢)

    1. 圈子中的互动功能的分析与设计 1.1 数据库的原则 经过我们分析,圈子中的互动数据有一下特点: 数据量非常大 数据变化迅速 数据价值相对较低 综上,我们采用MongoDB来存储圈子中的互动数据 ...

  3. 单身的看过来啦~圣诞节的圣诞树源码在这里,快做了发给喜欢的对象

    哈喽~大家好!我是恰恰,大家都知道马上就要圣诞节啦!想想都觉得挺期待的,我真是为你们操碎了心呢,有女朋友的小伙伴还不快来学会用Python做一个圣诞树给女朋友,当然啦,没有女朋友的,也可以学会了给喜欢 ...

  4. PTA|团体程序设计天梯赛-练习题库集

    文章目录 关于爬取脚本的编写 L1-001 Hello World! (5 分) L1-002 打印沙漏 (15 分) L1-003 个位数统计 (15 分) L1-004 计算摄氏温度 (5 分) ...

  5. 都说「三观不正」,那么正确的三观是怎样的 [转]

    都说「三观不正」,那么正确的三观是怎样的? 覺無,普通人会说:"人生本无意义,所以你要赋- 傲娇呸.fittnay.半月男 等人赞同 作为正确三观的官方代言人,我很负责的告诉大家~~: 世界 ...

  6. java intent 传递集合对象_Intent之对象传递(Parcelable传递对象和对象集合)

    接着上一篇文章,以下我们讨论一下怎样利用Parcelable实现Intent之间对象的传递 一.实现对象传递 首先创建User.java实现Parcelable接口: package org.yayu ...

  7. java的工作原理你知道吗_每天用Mybatis,但是Mybatis的工作原理你真的知道吗?

    近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了. 核心部件:SqlSession Executor Stat ...

  8. 安卓网页广告拦截_拦截烦人的网页广告,增加上网体验

    本号所有资源版权归原作者所有,如有侵权请加小编微信删除.本号免费分享,仅供学习交流,下载后24小时内请自觉删除,切勿用于商业用途,否则后果自负! 今天重新整理分享几大主流浏览器和IE浏览器拦截广告的方 ...

  9. 17个Python小窍门

    python中相对不常见却很实用的小窍门. 空谈不如来码代码吧: 交换变量值 给列表元素创建新的分隔符 找列表中出现次数最多的元素 核对两个字符是否为回文 反向输出字符串 反向输出列表 转置2维数组 ...

最新文章

  1. 19.04.27--作业 打字游戏
  2. 985博士:导师是院士,直到毕业,我们都没单独说过一句话
  3. swift LOG 输出
  4. 用指针实现删除数组中小于10的数据
  5. mongodb常用管理命令
  6. python基础课程第12章,Python基础教程学习笔记 | 第12章 | 图形用户界面
  7. 查找字符位置_Excel中查找字符第N次出现的位置信息,换个思路其实很简单
  8. 人脸口罩识别——人脸添加口罩方法masked_faces
  9. linux配置文件、日志文件全备份
  10. buidulbs android.jar,在将AS项目迁移到IDEA时,无法将com.android.bui...
  11. 服务器通过响应头向浏览器设置cookie,http响应包括设置cookie jession id,但随后发送请求,请求标头中没有cookie信息...
  12. python变量设置为true_如果为true,则从现在起在Python中忽略变量
  13. silverlight 自定义资源整理(待后续补充)
  14. c语言程序 题库管理,C语言程序设计题库管理.doc
  15. 云杰恒指:7.22恒指期货交易资讯复盘
  16. 容斥原理与广义容斥原理
  17. ABAC基于属性的访问控制
  18. Win10默认浏览器没有edge的选项该怎么办?
  19. ros--rosbag
  20. 上传大文件解决方案插件

热门文章

  1. cocos2d-x3.x实现屏幕画线并添加刚体属性
  2. win7原版映像中添加usb3.0驱动
  3. 211大学中哪几所计算机专业好,北京哪些211大学计算机专业比较好考研
  4. iPad mini7:库克终于舍得堆料了?
  5. 轻松掌握辗转相除法(原理+俩道简单编程题详解)
  6. 安卓桌面壁纸_火莹视频桌面:好玩的动态桌面壁纸软件,让你的桌面动起来
  7. 【知识图谱】通俗易懂的知识图谱技术
  8. 小试牛刀【自己翻译】
  9. C/C++ 时间知识总结
  10. 微波背景辐射的发现(获诺贝尔奖)——彭齐亚斯和威尔逊