• Shiro【散列算法、过滤器 、Shiro会话、会话管理器、权限表设计】(三)-全面详解(学习总结---从入门到深化)


     

    目录

    Shiro认证_散列算法

    Shiro认证_过滤器 

    Shiro认证_获取认证数据

    Shiro认证_Shiro会话

    Shiro认证_会话管理器

    Shiro认证_退出登录 

    Shiro认证_Remember Me 

     Shiro授权_权限表设计


    Shiro认证_散列算法

    散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,适 合于对密码进行加密。比如密码 admin ,产生的散列值是 21232f297a57a5a743894a0e4a801fc3 ,但在md5解密网站很容易的通过散列值 得到密码 admin 。所以在加密时我们可以加一些只有系统知道的干扰 数据,这些干扰数据称之为“盐”,并且可以进行多次加密,这样生 成的散列值相对来说更难破解。

     Shiro支持的散列算法:

    Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、 Sha384Hash、Sha512Hash

    1. @SpringBootTest
    2. public class Md5Test {
    3. @Test
    4. public void testMd5() {
    5. //使用MD5加密
    6. Md5Hash result1 = new Md5Hash("123");
    7. System.out.println("md5加密后的结果:" + result1);
    8. //加盐后加密,加密5次
    9. Md5Hash result2 = new Md5Hash("123","sxt",5);
    10. System.out.println("md5加盐加密后的结果:" + result2);
    11. }
    12. }

    接下来我们在项目中对密码进行加密:

    1、修改数据库和实体类,添加盐字段,并修改数据库用户密码为加盐加密后的数据。

    1. @Data
    2. public class Users{
    3. private Integer uid;
    4. private String username;
    5. private String password;
    6. private String salt;
    7. }

    2、修改自定义Realm

    1. @Component
    2. public class MyRealm extends AuthorizingRealm {
    3. @Autowired
    4. private UserInfoMapper userInfoMapper;
    5. // 自定义认证方法
    6. @Override
    7. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    8. // 1.获取用户输入的用户名
    9. UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    10. String username = token.getUsername();
    11. // 2.根据用户名查询用户
    12. QueryWrapper wrapper = new QueryWrapper().eq("username",username);
    13. Users users = usersMapper.selectOne(wrapper);
    14. // 3.将查询到的用户封装为认证信息
    15. if (users == null) {
    16. throw new UnknownAccountException("账户不存在");
    17. }
    18. /**
    19. * 参数1:用户
    20. * 参数2:密码
    21. * 参数3:盐
    22. * 参数4:Realm名
    23. */
    24. return new SimpleAuthenticationInfo(users,
    25. users.getPassword(),
    26. ByteSource.Util.bytes(users.getSalt()),
    27. "myRealm");
    28. }
    29. // 自定义授权方法
    30. @Override
    31. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    32. return null;
    33. }
    34. }

    3、在注册自定义Realm时添加加密算法

    1. // 配置加密算法
    2. @Bean
    3. public HashedCredentialsMatcher hashedCredentialsMatcher(){
    4. HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    5. //加密算法
    6. hashedCredentialsMatcher.setHashAlgorithmName("md5");
    7. //加密的次数
    8. hashedCredentialsMatcher.setHashIterations(5);
    9. return hashedCredentialsMatcher;
    10. }
    11. // Realm
    12. @Bean
    13. public MyRealm getMyRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
    14. MyRealm myRealm = new MyRealm();
    15. // 设置加密算法
    16. myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
    17. return myRealm;
    18. }

    4、启动项目,访问登录页http://localhost/login,测试登录功能。

    Shiro认证_过滤器 

     在以上案例中,虽然有认证功能,但即使没有登录也可以访问系统 资源。如果要配置认证后才能访问资源,就需要使用过滤器拦截请 求。Shiro内置了很多过滤器:

     过滤器工厂配置过滤器链:

    1. // 配置过滤器
    2. @Bean
    3. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
    4. // 1.创建过滤器工厂
    5. ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
    6. // 2.过滤器工厂设置SecurityManager
    7. filterFactory.setSecurityManager(securityManager);
    8. // 3.设置shiro的拦截规则
    9. Map filterMap=new HashMap<>();
    10. // 不拦截的资源
    11. filterMap.put("/login.html","anon");
    12. filterMap.put("/fail.html","anon");
    13. filterMap.put("/user/login","anon");
    14. filterMap.put("/css/**","anon");
    15. // 其余资源都需要用户认证
    16. filterMap.put("/**","authc");
    17. // 4.将拦截规则设置给过滤器工厂
    18. filterFactory.setFilterChainDefinitionMap(filterMap);
    19. // 5.登录页面
    20. filterFactory.setLoginUrl("/login.html");
    21. return filterFactory;
    22. }

    Shiro认证_获取认证数据

     用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显 示:欢迎您,XXX。获取用户信息的写法如下:

    1. @RequestMapping("/user/getUsername")
    2. @ResponseBody
    3. public String getUsername(){
    4. Subject subject = SecurityUtils.getSubject();
    5. // 获取认证数据
    6. Users users = (Users)subject.getPrincipal();
    7. return users.getUsername();
    8. }

    Shiro认证_Shiro会话

     Shiro提供了完整的企业级会话管理功能,不依赖于Web容器,不管 JavaSE还是JavaEE环境都可以使用。

    1. // 使用Shiro提供的会话对象
    2. @RequestMapping("/user/session")
    3. @ResponseBody
    4. public void session(){
    5. // 1.获取Subject
    6. Subject subject = SecurityUtils.getSubject();
    7. // 2.获取会话
    8. Session session = subject.getSession();
    9. // 会话id
    10. System.out.println("会话id:"+session.getId());
    11. // 会话的主机地址
    12. System.out.println("会话的主机地址:"+session.getHost());
    13. // 设置会话过期时间
    14. session.setTimeout(1000*10);
    15. // 获取会话过期时间
    16. System.out.println("会话过期时间:"+session.getTimeout());
    17. // 会话开始时间
    18. System.out.println("会话开始时间:"+session.getStartTimestamp());
    19. // 会话最后访问时间
    20. System.out.println("会话最后访问时间:"+session.getLastAccessTime());
    21. // 会话设置数据
    22. session.setAttribute("name","百战不败");
    23. }
    24. @RequestMapping("/user/getSession")
    25. @ResponseBody
    26. public void getSession(){
    27. Subject subject = SecurityUtils.getSubject();
    28. Session session = subject.getSession();
    29. System.out.println(session.getAttribute("name"));
    30. }

    Shiro认证_会话管理器

    Shiro中提供了会话管理器,可以对会话对象进行配置和监听,用法如下:

    1、创建会话监听器

    1. @Component
    2. public class MySessionListener implements SessionListener {
    3. //会话创建时触发
    4. @Override
    5. public void onStart(Session session) {
    6. System.out.println("会话创建:" + session.getId());
    7. }
    8. //会话过期时触发
    9. @Override
    10. public void onExpiration(Session session) {
    11. System.out.println("会话过期:" + session.getId());
    12. }
    13. //退出/会话过期时触发
    14. @Override
    15. public void onStop(Session session) {
    16. System.out.println("会话停止:" + session.getId());
    17. }
    18. }

    2、在会话管理器中配置会话监听器

    1. // 会话管理器
    2. @Bean
    3. public SessionManager
    4. sessionManager(MySessionListener sessionListener) {
    5. // 创建会话管理器
    6. DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    7. // 创建会话监听器集合
    8. List listeners = new ArrayList();
    9. listeners.add(sessionListener);
    10. // 将监听器集合设置到会话管理器中
    11. sessionManager.setSessionListeners(listeners);
    12. // 全局会话超时时间(单位毫秒),默认30分钟,设置为5秒
    13. sessionManager.setGlobalSessionTimeout(5*1000);
    14. // 是否开启删除无效的session对象,默认为true
    15. sessionManager.setDeleteInvalidSessions(true);
    16. // 是否开启定时调度器进行检测过期session,默认为true
    17. sessionManager.setSessionValidationSchedulerEnabled(true);
    18. return sessionManager;
    19. }

    3、在SecurityManager中配置会话管理器

    1. @Bean
    2. public DefaultWebSecurityManager securityManager(MyRealm myRealm,MyRealm2
    3. myRealm2,SessionManager sessionManager){
    4. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    5. // 自定义Realm放入SecurityManager中
    6. // securityManager.setRealm(myRealm);
    7. // 设置Realm管理者(需要设置在Realm之前)
    8. securityManager.setAuthenticator(modularRealmAuthenticator());
    9. List realms = new ArrayList();
    10. realms.add(myRealm);
    11. //realms.add(myRealm2);
    12. securityManager.setRealms(realms);
    13. securityManager.setSessionManager(sessionManager);
    14. return securityManager;
    15. }

    Shiro认证_退出登录 

    在系统中一般都有退出登录的操作。退出登录后Shiro会销毁会话和 认证数据。在Shrio中,退出登录的写法如下:

    1、编写退出登录控制器

    1. @RequestMapping("/user/logout")
    2. public String logout(){
    3. Subject subject = SecurityUtils.getSubject();
    4. // 退出登录
    5. subject.logout();
    6. // 退出后跳转到登录页
    7. return "redirect:/login";
    8. }

    2、在主页面添加退出登录按钮

    1. html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>主页面title>
    6. head>
    7. <body>
    8. <h1>主页面h1>
    9. <h2><a href="/user/logout">退出登录a>h2>
    10. body>
    11. html>

    Shiro认证_Remember Me 

    Remember Me为“记住我”功能,即登录成功后,下次访问系统时无 需重新登录。当使用“记住我”功能登录后,Shiro会在浏览器Cookie 中保存序列化后的认证数据。之后浏览器访问项目时会携带该 Cookie数据,这样不登录也可以完成认证。

    当然,为了安全起见,并不是所有资源都可以通过“记住我”访问。 比如在电商系统中,查询商品等操作可以不登录,但是支付时往往 需要重新登录,Shiro支持配置什么资源可以通过“记住我”访问。

     实现“记住我”功能的写法如下:

    1、序列化所有实体类

    1. @Data
    2. public class Users implements Serializable
    3. {
    4. private Integer uid;
    5. private String username;
    6. private String password;
    7. private String salt;
    8. }

    2、配置Cookie生成器和记住我管理器

    1. // Cookie生成器
    2. @Bean
    3. public SimpleCookie simpleCookie() {
    4. SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    5. // Cookie有效时间,单位:秒
    6. simpleCookie.setMaxAge(20);
    7. return simpleCookie;
    8. }
    9. // 记住我管理器
    10. @Bean
    11. public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie) {
    12. CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    13. // Cookie生成器
    14. cookieRememberMeManager.setCookie(simpleCookie);
    15. // Cookie加密的密钥
    16. cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));
    17. return cookieRememberMeManager;
    18. }
    19. @Bean
    20. public DefaultWebSecurityManager securityManager(MyRealm myRealm,
    21. MyRealm2 myRealm2,SessionManager sessionManager,
    22. CookieRememberMeManager rememberMeManager){
    23. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    24. // 自定义Realm放入SecurityManager中
    25. // securityManager.setRealm(myRealm);
    26. // 设置Realm管理者(需要设置在Realm之前)
    27. securityManager.setAuthenticator(modularRealmAuthenticator());
    28. List realms = new ArrayList();
    29. realms.add(myRealm);
    30. //realms.add(myRealm2);
    31. securityManager.setRealms(realms);
    32. securityManager.setSessionManager(sessionManager);
    33. securityManager.setRememberMeManager(rememberMeManager);
    34. return securityManager;
    35. }

    3、修改登录表单

    1. <form class="form" action="/user/login" method="post">
    2. <input type="text" placeholder="用户名" name="username">
    3. <input type="password" placeholder="密码" name="password">
    4. <input type="checkbox" name="rememberMe" value="on">记住我<br>
    5. <button type="submit" id="loginbutton">登录button>
    6. form>

    4、修改登录控制器

    1. @RequestMapping("/user/login")
    2. public String login(String username,String password,String rememberMe) {
    3. try {
    4. usersService.userLogin(username,password,rememberMe);
    5. return "main";
    6. } catch (DisabledAccountException e) {
    7. System.out.println("账户失效");
    8. return "fail";
    9. } catch (ConcurrentAccessException e){
    10. System.out.println("竞争次数过多");
    11. return "fail";
    12. } catch (ExcessiveAttemptsException e){
    13. System.out.println("尝试次数过多");
    14. return "fail";
    15. } catch (UnknownAccountException e) {
    16. System.out.println("用户名不正确");
    17. return "fail";
    18. } catch (IncorrectCredentialsException e) {
    19. System.out.println("密码不正确");
    20. return "fail";
    21. } catch (ExpiredCredentialsException e) {
    22. System.out.println("凭证过期");
    23. return "fail";
    24. }
    25. }

    5、修改登录Service

    1. @Service
    2. public class UsersService {
    3. @Autowired
    4. private DefaultWebSecurityManager securityManager;
    5. public void userLogin(String username,String password,String rememberMe) throws AuthenticationException
    6. {
    7. SecurityUtils.setSecurityManager(securityManager);
    8. Subject subject = SecurityUtils.getSubject();
    9. UsernamePasswordToken token=new UsernamePasswordToken(username,password);
    10. if (rememberMe != null){
    11. // 如果用户选择记住我,则生成记住我Cookie
    12. token.setRememberMe(true);
    13. }
    14. subject.login(token);
    15. }
    16. }

    6、配置过滤器,配置可以通过“记住我”访问的资源。

    1. @Bean
    2. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
    3. // 1.创建过滤器工厂
    4. ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
    5. // 2.过滤器工厂设置SecurityManager
    6. filterFactory.setSecurityManager(securityManager);
    7. // 3.设置shiro的拦截规则
    8. Map filterMap=new HashMap<>();
    9. // 不拦截的资源
    10. filterMap.put("/login.html","anon");
    11. filterMap.put("/fail.html","anon");
    12. filterMap.put("/user/login","anon");
    13. filterMap.put("/static/**","anon");
    14. // 其余资源都需要认证,authc过滤器表示需要认证才能进行访问;
    15. //user过滤器表示配置记住我或认证都可以访问
    16. //filterMap.put("/**","authc");
    17. filterMap.put("/user/pay","authc");
    18. filterMap.put("/**", "user");
    19. // 4.将拦截规则设置给过滤器工厂
    20. filterFactory.setFilterChainDefinitionMap(filterMap);
    21. // 5.登录页面
    22. filterFactory.setLoginUrl("/login.html");
    23. return filterFactory;
    24. }

    7、编写支付控制器

    1. // 支付
    2. @RequestMapping("/user/pay")
    3. @ResponseBody
    4. public String pay(){
    5. return "支付功能";
    6. }
  • 相关阅读:
    LeetCode 练习——剑指 Offer 66. 构建乘积数组
    2014年3月13日 Go生态洞察:并发模式与管道取消技术
    如何使用 Terraform 和 Git 分支有效管理多环境?
    LeetCode·376.摆动序列·贪心·动态规划
    【SpringBoot】整合第三方技术
    安装anaconda时控制台conda-version报错
    标志寄存器
    Java方法的使用
    视频号视频怎么保存?教你三种方法
    专业课140+总分420+东南大学920专业综合考研,信息学院通信专业考研分享
  • 原文地址:https://blog.csdn.net/m0_58719994/article/details/131670807