shiro注册登录流程(如何加密加盐)+配置多个Ream+密码加密验证底层分析+Remember使用+不同密码比对器原理(二)

1.注册

1.1 注册流程

  1. 用户输入用户名密码进行注册。

  2. 服务端收到用户名密码之后,进行一个加密处理,然后再存到数据库中。

    //带盐的加密字符串
    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 登录流程

  1. 登录的时候用户输入用户名密码。
  2. 登录时候,主要在 com.qfedu.demo.realm.DbRealm#doGetAuthenticationInfo 方法中处理登录逻辑,这个方法主要是根据登录用户名去数据库查询到用户信息,并返回。
  3. 系统会自动调用 com.qfedu.demo.realm.DbRealm#getCredentialsMatcher 方法中提供的密码比对器,做最后的密码比对操作。

3.登录加密验证

3.1数据库

在这里插入图片描述

我们这里设置六个字段

  1. id 用户id
  2. username用户名 —>以后都是用用户名进行加盐操作
  3. password用户密码—> 密码以后要进行加密,有两种加密方法
    1. MD5
    2. SHA-512
  4. nickname暂时用不到
  5. 权限名(boolean类型)1,0
  6. 用户权限类型

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缓存中去,这样下一次我们就不用执行这一些代码,

在这里插入图片描述

  1. info返回信息:info如果不等于Null,即我们的info是有值的话怎么返回info信息给我们的DBrealm里面的调用方法,通过assertCredentialsMatch()进行密码的比对!

在这里插入图片描述

要是token和info不相同的话,就去抛异常,那密码是怎么进行比对的呢?

  1. 进入**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登录流程

  1. 登录的时候,传入用户名密码,登录成功之后,先对当前登录的用户进行序列化,序列化之后,得到一个 byte 数组,然后对这个 byte 数组进行加密(用的是 AES 对称加密,将来可以解密的),加密之后得到一个 byte 数组,但是这个 byte 数组无法直接展示出来,需要再进行一次 Base64 转码,就可以转为可读的字符串了,然后将这个字符串写入到 Cookie 中,并返回给浏览器。

5.2 验证流程

  1. 以后每次请求的时候,系统都会自动携带上这个 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 还是会认证的,



此时 FirstSuccessfulStrategyAtLeastOneSuccessfulStrategy 其实没有差别。
如果将 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一样

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您可以参考以下代码实现: ``` import org.apache.shiro.authc.*; import org.apache.shiro.realm.Realm; import org.apache.shiro.util.ByteSource; import org.apache.shiro.util.StringUtils; import java.util.HashSet; import java.util.Set; public class MyRealm implements Realm { private static final String REALM_NAME = "MyRealm"; private static Set<User> users = new HashSet<>(); static { // 模拟两个用户 User user1 = new User("user1", "123456"); User user2 = new User("user2", "123456"); users.add(user1); users.add(user2); } @Override public String getName() { return REALM_NAME; } @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); // 根据用户名从数据库查询用户信息 User user = getUserByUsername(username); if (user == null) { throw new UnknownAccountException(); // 用户不存在 } // 判断密码是否匹配 String md5Password = ShiroUtils.md5(password, user.getSalt()); // 加盐MD5加密 if (!StringUtils.equals(md5Password, user.getPassword())) { throw new IncorrectCredentialsException(); // 密码错误 } // 验证通过,返回一个封装了用户信息的AuthenticationInfo对象 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, md5Password, getName()); info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt())); // 设置盐值 return info; } /** * 根据用户名从数据库查询用户信息 */ private User getUserByUsername(String username) { for (User user : users) { if (StringUtils.equals(username, user.getUsername())) { return user; } } return null; } /** * 用户类,包含用户名和密码 */ private static class User { private String username; private String password; private String salt; public User(String username, String password) { this.username = username; this.password = password; this.salt = ShiroUtils.getRandomSalt(); // 生成一个随机盐值 } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } } } ``` 其中,ShiroUtils类中的md5和getRandomSalt方法的实现如下: ``` import org.apache.shiro.codec.Hex; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.util.ByteSource; import java.security.SecureRandom; public class ShiroUtils { /** * 对密码进行加盐MD5加密 * * @param password 密码 * @param salt 盐值 * @return 加密后的密码 */ public static String md5(String password, String salt) { return new Md5Hash(password, ByteSource.Util.bytes(salt), 2).toHex(); } /** * 生成一个随机盐值 * * @return 盐值 */ public static String getRandomSalt() { SecureRandom random = new SecureRandom(); byte[] saltBytes = new byte[16]; random.nextBytes(saltBytes); return Hex.encodeToString(saltBytes); } } ``` 调用方式如下: ``` // 创建一个SecurityManager DefaultSecurityManager securityManager = new DefaultSecurityManager(); // 设置Realm securityManager.setRealm(new MyRealm()); // 将SecurityManager设置到当前线程的环境中 SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建一个用户名密码令牌 UsernamePasswordToken token = new UsernamePasswordToken("user1", "123456"); // 登录,即进行身份验证 currentUser.login(token); // 验证是否登录成功 if (currentUser.isAuthenticated()) { System.out.println("登录成功"); } else { System.out.println("登录失败"); } ``` 其中,UsernamePasswordToken的构造方法可以传入用户名和密码,也可以传入用户名、密码和是否记住我。您可以根据实际需要进行选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值