• Shiro


    1.shiro简介

    官网介绍主要实现四个功能:

    1. Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.

    2. Authorization: The process of access control, i.e. determining ‘who’ has access to ‘what’.

    3. Session Management: Managing user-specific sessions, even in non-web or EJB applications.

    4. Cryptography: Keeping data secure using cryptographic algorithms while still being easy to use.

    2.架构

    三个概念:

    1.Subject:当前用户,可以是一个人也可是服务,表示与当前软件交互的任何事件

    2.SecurityManager:管理所有Subject,为Shiro架构的核心

    3.Realms:用于进行权限信息的验证,由自己实现

    3.配置

    1.编写ShiroConfig配置类,用到注解@Configuration交由spirng管理,通过url来进行过滤和权限划分

    首先new出ShiroFilterFactoryBean 将securityManager添加进去,再设置登录路径、成功路径及无权限登录路径

     

    // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
            shiroFilterFactoryBean.setLoginUrl("/login");
            shiroFilterFactoryBean.setSuccessUrl("/index/main");
            // 设置无权限时跳转的 url;
            shiroFilterFactoryBean.setUnauthorizedUrl("/error/404");

    添加url规则Map

    // 设置拦截器
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            //游客,开发权限
    ​
            filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/druid/**", "anon");
    ​
            //开放登陆接口
            filterChainDefinitionMap.put("/main", "anon");
            filterChainDefinitionMap.put("/login", "authc");
            filterChainDefinitionMap.put("/index/logout", "logout");
            //其余接口一律拦截
            //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
             filterChainDefinitionMap.put("/**", "user,sysUser");
    ​
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    2.注入Realm

     @Bean
        public CustomRealm customRealm() {
            CustomRealm customRealm = new CustomRealm();
            customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            customRealm.setCachingEnabled(false);
            return customRealm;
        }

    其中有个加密方法

        @Bean
      public HashedCredentialsMatcher hashedCredentialsMatcher() {
          /*授权匹配 */
          HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
          hashedCredentialsMatcher.setHashAlgorithmName("MD5");
          hashedCredentialsMatcher.setHashIterations(2);
          return hashedCredentialsMatcher;
      }

    散列两次的加密方式,参考

    3.注入SecurityManager

       /**
         * 注入 securityManager
         */
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setAuthenticator(authenticator());
    ​
            // 设置realm.
            securityManager.setRealms(getRealms());
            return securityManager;
        }

    这个也是将Realms集合添加进去

    4.自定义Realm

    package com.btw.config.shiro;
    ​
    import com.btw.entity.sys.User;
    import com.btw.service.sys.UserService;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.SimplePrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    ​
    import javax.annotation.Resource;
    ​
    public class CustomRealm extends AuthorizingRealm {
    ​
        @Resource
        private UserService userService;
    ​
    ​
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    ​
                // 从数据库获取对应用户名密码的用户
                User user = userService.findUserByLoginName(token.getUsername());
                String url = new String((char[]) token.getCredentials());
                if (null == user) {
                    throw new AccountException("用户名不正确");
                }
                if (user.isLocked()) {
                    throw new LockedAccountException(); //帐号锁定
                }
                ByteSource credentialsSalt = ByteSource.Util.bytes(user.getLoginName());//使用账号作为盐值
                SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                        user.getLoginName(),//用户名
                        user.getPasswd(),//密码
                        credentialsSalt,
                        getName()  //realm name
                );
                return authenticationInfo;
        }
    ​
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = (String) principals.getPrimaryPrincipal();
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            authorizationInfo.setRoles(userService. findRoles(username));  //角色
            authorizationInfo.setStringPermissions(userService.findPermissions(username));  //权限
            return authorizationInfo;
        }
    ​
        public void removeUserAuthorizationInfoCache(String username) {
            SimplePrincipalCollection pc = new SimplePrincipalCollection();
            pc.add(username, super.getName());
            super.clearCachedAuthorizationInfo(pc);
        }
    }
    View Code

    第一个doGetAuthenticationInfo()方法为登录认证的实现

    该方法主要执行以下操作:

    • 1、检查提交的进行认证的令牌信息

    • 2、根据令牌信息从数据源(通常为数据库)中获取用户信息

    • 3、对用户信息进行匹配验证。

    • 4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。

    • 5、验证失败则抛出AuthenticationException异常信息。

       

    第二个doGetAuthorizationInfo()方法为授权的实现

    set 集合:roles 是从数据库查询的当前用户的角色,stringPermissions 是从数据库查询的当前用户对应的权限

    就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。

    5.登录接口

    这个主要是处理异常的相关信息

    ​
    @Controller
    @Slf4j
    public class LoginController {
    ​
        private final static String errorAttributeName = "shiroLoginFailure";
    ​
        @Autowired
        private UserService userService;
    ​
        // 五分钟
        private ExpiryMap<String, Integer> resetMap = new ExpiryMap<>(1000 * 60 * 5);
    ​
        @RequestMapping(value = "/login")
        public String showLoginForm(HttpServletRequest req, Model model, @RequestParam(value = errorAttributeName, required = false) String errorMsg) {
    ​
            String exceptionClassName = (String) req.getAttribute(errorAttributeName);
            String error;
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                error = "用户名/密码错误"; //账户不存在
            } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
                error = "用户名/密码错误";
            } else if (ExcessiveAttemptsException.class.getName().equals(exceptionClassName)) {
                error = "密码错误次数已达上限(3次),请稍后再试";
            } else if (exceptionClassName != null) {
                error = "用户名/密码错误";
            } else {
                error = errorMsg;
            }
            model.addAttribute("error", error);
            return "login";
        }
    ​
        private boolean isAuthenticated() {
            return SecurityUtils.getSubject().isAuthenticated();
        }
    ​
    ​
    }
    View Code

    6.前置controller

    @Controller
    public class ForwardPageController {
    
        private final String NULL_PAGE_URL = "error/404";
    
        @RequestMapping("/main")
        public String mainPage(HttpServletRequest request) {
            String forwardUrl = "forward:/";
            String userId = request.getParameter("userId");
            String url = request.getParameter("page");
            // 查找用户
            if (StringUtils.isNotBlank(userId)) {
                try {
                    // 从SecurityUtils里边创建一个 subject
                    Subject subject = SecurityUtils.getSubject();
                   String decrypt = AESUtils.getDefaultNoPadding().decrypt(userId).trim();
                   // String decrypt = userId;
                    // 在认证提交前准备 token(令牌)
                    UsernamePasswordToken token = new UsernamePasswordToken(decrypt, url);
                    // 执行认证登陆
                    subject.login(token);
                    boolean authenticated = subject.isAuthenticated();
                    if (authenticated) {
                        subject.getSession().setAttribute("page", url);
                        forwardUrl = forwardUrl + url;
                        return forwardUrl;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            forwardUrl = forwardUrl + NULL_PAGE_URL;
            return forwardUrl;
        }
    }

    前台传递两个参数,一个page为路径,还有一个userId为用户名

    创建Subject对象,用AES加密算法将用户名加密,然后创建令牌,获取Page参数的Url,跳转页面


    tips:

    public static void main(String[] args) {
      String hashAlgorithmName = "md5";//加密方式
      Object crdentials = "123456";//密码原值
      Object salt = "admin";//盐值
      int hashIterations = 2;//散列次数
      SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, crdentials, salt, hashIterations);
      System.out.println(simpleHash);
    }
  • 相关阅读:
    java基础(上)
    java前奏
    Spring框架介绍
    bootstrap简单学习
    存储过程和函数
    触发器
    视图
    索引
    mysql增、删、改数据
    子查询
  • 原文地址:https://www.cnblogs.com/wutongshu-master/p/11771828.html
Copyright © 2020-2023  润新知