Shiro

摘要:
1.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;
  }

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

[https://muguang.me/it/2721.html​]  Shiro的多次散列 

3.注入SecurityManager

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

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

4.自定义Realm

Shiro第1张Shiro第2张
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.登录接口

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

Shiro第3张Shiro第4张
​
@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);
}

免责声明:文章转载自《Shiro》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇value optimized out的问题[翻译]如何编写GIMP插件(二)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C#中的String类2

                          深入C# String类 C#中的String类 他是专门处理字符串的(String),他在System的命名空间下,在C#中我们使用的是string 小写的string只是大写的String的一个别名(外号)使用大写和小写都是一样的 Using  == import 常用的字符串处理法 Java中常用的...

交易所如何对接狗狗币(DOGE)钱包?这点不可忽视

一个大家非常熟悉的 meme 头像 doge 在一夜之间成为了全球热搜。在国内,「狗狗币一天暴涨逾 250%」登上了微博热搜第七位,在国外,「DOGE」或者「DOGECOIN」站上了多个地区的「推特趋势榜」。比特币似乎都没有如此高规格的待遇。   对于新入币圈的朋友们可能对狗狗币还不太熟悉。这个币是个很早期的山寨币,它基本上是比特币的翻版,但是在比特币的基...

java--枚举

前言   java中enum其实也是一种class类型,他和一般的class不同的是    1.全局只有一个实例    2.不能拥有public构造函数    3.无法继承和被继承 枚举案例 public enum HttpCode { SUCCESS(200, "操作成功"),//每定义一个枚举项,就相当通过构造函数HttpCode(int co...

C#--反射基础

以下是学习笔记: 一,反射的基本信息 DLL/EXE: 主要区别EXE文件有一个入口,DLL文件不能运行,但是DLL能拿到其他地方去使用 metadata(元数据):描述exe/dll文件的一个清单,记录了exe/dll文件中有哪些类,属性,特性,字段。。。 Reflection(反射):用来操作或获取元数据metadata 有什么作用: 1,更新程序(...

块状链表[ext/rope]

2008年OI集训论文上有介绍<对块状链表的一点研究>,其主要是结合了链表和数组各自的优点,链表中的节点指向每个数据块,即数组,并且记录数据的个数,然后分块查找和差入。在g++头文件中,<ext/rope>中有成型的块状链表,在using namespace __gnu_cxx;空间中,其操作十分方便。 基本操作: rope lis...

C#正则表达式合并连续空格为单个空格

第一种方法:       使用 System.Text.RegularExpressions.Regex.Replace()方法   string result = String.Empty;   string str = "Just     Test the  Method";   result = Regex.Replace(str, "\s{2,}"...