1.注册
1.1 注册流程
-
用户输入用户名密码进行注册。
-
服务端收到用户名密码之后,进行一个加密处理,然后再存到数据库中。
//带盐的加密字符串 Md5Hash zhangsan = new Md5Hash("123", "zhangsan", 1024); System.out.println("zhangsan = " + zhangsan); SimpleHash simpleHash = new SimpleHash("MD5", "123", "lisi", 1024); System.out.println("simpleHash = " + simpleHash);
加密之后,将加密后的密码存入到数据库中。
2.登录
2.1 登录流程
- 登录的时候用户输入用户名密码。
- 登录时候,主要在
com.qfedu.demo.realm.DbRealm#doGetAuthenticationInfo
方法中处理登录逻辑,这个方法主要是根据登录用户名去数据库查询到用户信息,并返回。 - 系统会自动调用
com.qfedu.demo.realm.DbRealm#getCredentialsMatcher
方法中提供的密码比对器,做最后的密码比对操作。
3.登录加密验证
3.1数据库
我们这里设置六个字段
- id 用户id
- username用户名 —>以后都是用用户名进行加盐操作
- password用户密码—> 密码以后要进行加密,有两种加密方法
- MD5
- SHA-512
- nickname暂时用不到
- 权限名(boolean类型)1,0
- 用户权限类型
3.2 密码加密过程
由于我们这里没有写登录功能,我们在测试方法里手动进行密码加密,更便于理解
MainTest
//第一种加密方法,写法不同, 利用 MD5 消息摘要计算 123 的消息摘要
Md5Hash md5Hash = new Md5Hash("123");
System.out.println("md5Hash = " + md5Hash);
//也可以指定SimpleHash,因为MD5Hash是SimpleHash的子类
SimpleHash md5 = new SimpleHash("MD5", "123");
System.out.println("md5 = " + md5);
//第二种加密方法,写法不同,利用SHA512信息摘要计算123的信息摘要
System.out.println("-------------------------------------");
System.out.println("new Sha512Hash(\"123\") = " + new Sha512Hash("123"));
System.out.println("new SimpleHash(\"SHA-512\",\"123\") = " + new SimpleHash("SHA-512", "123"));
密码加密结果:
3.3 controller层如何调用密码加密并且验证
@Component
public class DBRealm extends AuthenticatingRealm {
@Autowired
UserMapper userMapper;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken toKen) throws AuthenticationException {
//当前登录的用户名和密码都在token里面保存
UsernamePasswordToken usernamePasswordToken =(UsernamePasswordToken) toKen;
String username = usernamePasswordToken.getUsername();
Users users = userMapper.getUserByUsername(username);
if (users == null){
throw new UnknownAccountException("用户名输入错误");
}
if (!users.getEnabled()){
throw new LockedAccountException("账号被锁定,登录失败");
}
/*传入方法的参数:
1.用户登录的用户名
* 2.用户的密码,注意,这个密码是从数据库查询出来的用户密码
* 3.这里返回之后,系统会根据这里返回的信息,再结合用户登录时输入的用户名密码信息去判断密码是否正确
* */
return new SimpleAuthenticationInfo(users.getUsername(), users.getPassword(),getName());
}
@Override
public CredentialsMatcher getCredentialsMatcher() {
//这里返回的是SHA-512加密方式的密码的信息摘要给数据库,所以我们要手动给数据库的password中添加,MainTest的SHA-512加密方式的信息摘要
return new HashedCredentialsMatcher("SHA-512");
}
3.4补充1( MD5Hash第二种加密的原因):
MD5Hash是SimpleHash的子类,所以我们也可以用SimpleHash去进行MD5的加密,但是要添加加密方式
补充2:
3.5 Shiro底层之密码如何比对
第一步:首先我们在登录界面进入token,因为我们的用户名和密码都是存储在这个token里面
第二步:
第三步:
我们controller层的方法,返回的是同一个AuthentiationInfo
第四步骤
我们来看看authenticate方法做了什么
补充: 如果我们设置了多个Realm,就要跟第六小节一样去配置多个Realm,我们这里只写了一个Realm
第五步 再进入方法
getRealms方法的作用:这个方法来进行判断我们有几个Realms,
如果有多个就进入到多个的处理逻辑,
即doMultiRealmAuthentication这个里面去
有单个就进入到单个的处理逻辑。
即doSingleRealmAuthentication这个里面去
我们这里只有一个Realm,就先进入单个处理逻辑的方法
第六步 进入单个处理逻辑doSingleRealmAuthentication
第七步:进入这个doGetAuthenticationInfo的父类
GetAuthenticationInfo看他做了什么
这样就顺利的调用到了我们的DBRealm
第八步:我们的DBrealm返回了什么呢
第九步:继续看返回了什么
9.1.得到了返回的info
9.2 这个info里包含了我们的认证信息(从数据中传出来的)
9.3 然后再把这个(后端)info和(前端)token丢入我们的cache缓存中去,这样下一次我们就不用执行这一些代码,
- info返回信息:info如果不等于Null,即我们的info是有值的话怎么返回info信息给我们的DBrealm里面的调用方法,通过assertCredentialsMatch()进行密码的比对!
要是token和info不相同的话,就去抛异常,那密码是怎么进行比对的呢?
- 进入**assertCredentialsMatch()**方法看这个方法怎么进行密码的比对
11.1 我们在我们的AuthenticatingRealm里面也就是我们的DBRealm里调用了
getCredentialsMatcher()
这是一个接口对接了我们的密码比对器(shrio默认提供的)
SimpleCredentialsMatcher这个就是默认提供给我们的密码比对器
那我们的密码比对器做了什么
12.进入密码比对器SimpleCredentialsMatcher
步骤:
1.从token中拿到用户的数据数组
2.accountcredentials中拿到用户登录的字符串
3.equal方法比较两者值是否相等
13.equals比较
4.登录验证+加密+加盐
加密+加盐方式与上面大同小异但是不同的是要多加一个参数,也是用到上面的SimpleHash或者它的子类们。
//带盐的加密字符串
//第一种MD5加密方法加密,加盐
Md5Hash sang = new Md5Hash("123", "sang", 1024);
System.out.println("sang = " + sang);
System.out.println("--------------------------------------------------");
System.out.println("--------------------------------------------------");
//第二种SHA-512加盐加密
//加密加盐
Sha512Hash sha512Hash=new Sha512Hash("123","sang",1024);
System.out.println("--------------------------------------------------");
SimpleHash simpleHash = new SimpleHash("SHA-512", "123", "sang", 1024);
System.out.println("simpleHash = " + simpleHash);
controller调用方式与上面类似
5.RememberMe
概念:在服务端对所有的接口做一个分类,不涉及到数据修改的安全性较低的设置RememberMe
注意:RememberMe 也是一种认证的方式,不一定说所有的登录都有用账户名和密码进行登录(是便捷和用户安全之间的一种平衡)
5.1登录流程
- 登录的时候,传入用户名密码,登录成功之后,先对当前登录的用户进行序列化,序列化之后,得到一个 byte 数组,然后对这个 byte 数组进行加密(用的是 AES 对称加密,将来可以解密的),加密之后得到一个 byte 数组,但是这个 byte 数组无法直接展示出来,需要再进行一次 Base64 转码,就可以转为可读的字符串了,然后将这个字符串写入到 Cookie 中,并返回给浏览器。
5.2 验证流程
- 以后每次请求的时候,系统都会自动携带上这个 Cookie,系统收到这个 Cookie 之后,首先从请求头中提取出 RememberMe 字符串,提取出来之后首先进行 Base64 解码,解码之后,再调用 AES 服务进行解密,揭秘之后得到一个 byte 数组,再将这个 byte 数组反序列化,得到登录的用户对象。
5.3 用法
login.jsp中添加 参数
<form action="/m/doLogin" method="post">
<input type="text" name="username" value="sang">
<input type="password" name="password" value="123">
<input type="checkbox" value="true" name="rememberMe">rememberMe
<input type="submit" value="登录">
</form>
LoginController
@PostMapping("/doLogin")
//1.字符串传入参数(因为里面肯定带了数据所以设置为String类型)
public String doLogin(String username, String password, Model model,String rememberMe) {
//获取当前用户
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//2.登录操作之前
if ("on".equals(rememberMe)){
token.setRememberMe(true);
}
subject.login(token);
return "redirect:/hello";
} catch (AuthenticationException e) {
e.printStackTrace();
model.addAttribute("error", e.getMessage());
}
return "forward:/login";
}
注意:
1.字符串传入参数(因为里面肯定带了数据所以设置为String类型)
2.使用密码比对器进行密码比对前,先对获取到的携带RememberMe 的数据进行判断,是否可以使用
6.配置多个Realm
6.1 使用场景:
当商家和用户登录权限相同,管理员跟上面两者权限不同的时候,用一个接口来处理商家和用户登录权限
6.2 Realm的配置
1.AtLeastOneSuccessfulStrategy (org.apache.shiro.authc.pam):至少有一个 Realm 认证成功,就算成功
2.AllSuccessfulStrategy (org.apache.shiro.authc.pam):所有 Realm 都要认证成功才算成功
3.FirstSuccessfulStrategy (org.apache.shiro.authc.pam):至少有一个 Realm 认证成功,就算成功,这个 Bean 中,有一个属性名为 stopAfterFirstSuccess,表示是否在第一个 bean 认证成功后,就不再执行后面的 Realm 了,这个属性默认为 false,即第一个 Reaml 认证成功后,后面的 Realm 还是会认证的.
此时 FirstSuccessfulStrategy 和 AtLeastOneSuccessfulStrategy 其实没有差别。
如果将 stopAfterFirstSuccess 属性设置为 true,
那么对于 FirstSuccessfulStrategy 而言,
第一个 Realm 认证成功后,后面的 Realm 就不再执行了。
<!--
这个是配置多 Realm 的认证器
AtLeastOneSuccessfulStrategy (org.apache.shiro.authc.pam):至少有一个 Realm 认证成功,就算成功
AllSuccessfulStrategy (org.apache.shiro.authc.pam):所有 Realm 都要认证成功才算成功
FirstSuccessfulStrategy (org.apache.shiro.authc.pam):至少有一个 Realm 认证成功,就算成功,
这个 Bean 中,有一个属性名为 stopAfterFirstSuccess,
表示是否在第一个 bean 认证成功后,就不再执行后面的 Realm 了,
这个属性默认为 false,
即第一个 Reaml 认证成功后,
后面的 Realm 还是会认证的,
此时 FirstSuccessfulStrategy 和 AtLeastOneSuccessfulStrategy 其实没有差别。
如果将 stopAfterFirstSuccess 属性设置为 true,
那么对于 FirstSuccessfulStrategy 而言,
第一个 Realm 认证成功后,后面的 Realm 就不再执行了。
-->
<bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator" id="authenticator">
<!--设置多个 realm -->
<property name="realms">
<list>
<ref bean="DBRealm"/>
<ref bean="DBSaleMi"/>
</list>
</property>
<!--
AtLeastOneSuccessfulStrategy,身份验证类
-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
</property>
</bean>
<!--配置Realm-->
<!-- <bean class="com.huang.demo.realm.DBRealm" id="dbRealm"/>-->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="DBRealm"/>
<property name="sessionManager" >
<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdUrlRewritingEnabled" value="false"/>
</bean>
</property>
</bean>
<bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login" />
<property name="filterChainDefinitions">
<!--
/login=anon 表示 /login 这个路径可以匿名访问
/logout=logout 后面的 logout 是注销登录过滤器的简称
/**=authc 表示其余的请求,都要认证之后才能访问
注意这里的顺序
-->
<value>
/logout=logout
/login=anon
/doLogin=anon
/**=authc
</value>
</property>
</bean>
7.不同的密码比对器
SimplecredentialsMatcher
密码比对器
我们通过SimplecredentialsMatcher密码比对器进行密码比对,他的下面有很多的不同的密码比对的方法。那我们改怎么使用他?
注意:小节3中的例子是没有经过加密的,所有我们可以直接去使用SimplecredentialsMatcher这个密码比对器进行密码比对,
- 但是如果我们使用了MD5进行加密。那我们就要去使用SimplecredentialsMatcher的子类Md5CredentialsMatcher()
- 注意:这个方法废弃了
- 我们使用return new HashedCredentialsMatcher(“MD5”)的方式
- 用SHA-512一样
/login=anon
/doLogin=anon
/**=authc
</value>
</property>
</bean>
# 7.不同的密码比对器
SimplecredentialsMatcher
密码比对器
[外链图片转存中...(img-azXihmGW-1657209765342)]
我们通过SimplecredentialsMatcher密码比对器进行密码比对,他的下面有很多的不同的密码比对的方法。那我们改怎么使用他?
> 注意:小节3中的例子是没有经过加密的,所有我们可以直接去使用SimplecredentialsMatcher这个密码比对器进行密码比对,
>
> + 但是如果我们使用了MD5进行加密。那我们就要去使用SimplecredentialsMatcher的子类Md5CredentialsMatcher()
> + 注意:这个方法废弃了
> + 我们使用return new HashedCredentialsMatcher("MD5")的方式
> + 用SHA-512一样