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);
}
原文地址:https://www.cnblogs.com/wutongshu-master/p/11771828.html