zoukankan      html  css  js  c++  java
  • shiro

    shiro:

    1、介绍

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。

    2、组成

    img

     

    img

    shiro包含三个核心组件:Subject,SecurityManager,Realms
    其中:
    subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证,授权。
    securityManager:安全管理器,主体进行认证和授权都是通过securityManager进行。
    authenticator:认证器,用户身份验证组件,登录控制。
    authorizer:授权器,主体进行授权最终通过authorizer进行的。
    sessionManager:shiro提供的session的管理的方式
    SessionDao:通过SessionDao管理Session数据,使用与存储。
    cacheManager:缓存管理器,对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
    relam:域,相当于数据源,通过relam存取认证,授权相关数据。
    cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

    3、登录功能的实现(包含免密登录)

    登录流程:
    1、应用程序构建了一个终端用户认证信息的AuthenticationToken实例后,调用Subject.login方法
    2、Sbuject会委托应用程序设置的securityManager实例调用securityManager.login(token)方法。
    3、SecurityManager接受到token(令牌)信息后会委托内置的Authenticator的实例(通常都是ModularRealmAuthenticator类的实例)调用authenticator.authenticate(token).ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配,它实际上为Shiro提供了一个可拔插的认证机制。
    4、如果在应用程序中配置了多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果作出响应。 
    注:如果应用程序中仅配置了一个Realm,Realm将被直接调用而无需再配置认证策略。 
    5、Realm将调用getAuthenticationInfo(token);getAuthenticationInfo方法就是实际认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。

    img

     

    代码实现:

    Controller:
    @PostMapping("/login")
    public String login(HttpSession session, @RequestBody User user) throws Exception {

        String username = user.getUsername();
        String companyName = user.getCompanyName();
        username = HtmlUtils.htmlEscape(username);
        User serviceByUsername = null;
        //更具用户名查询用户
        serviceByUsername = userService.findByUsername(username);

    Subject subject = SecurityUtils.getSubject();
        String decodePassword = CryptTool.decodePw(user.getPassword());
    //因为业务需求不用,所以这里用用户Id和密码作为UsernamePasswordToken的参数
        UsernamePasswordToken token = new UsernamePasswordToken(serviceByUsername.getId(), decodePassword);
        token.setRememberMe(true);

        try {
            subject.login(token);
            return jsonSuccess(byUsername, "登录成功");
        } catch (AuthenticationException e) {
            logger.error("登录失败:"+e.toString());
            return jsonFailure("104", "登录失败,请联系管理员");
        }
    }

    subject.login(token)的源码:

     

     

     

    shiro目录结构:(其中AuthenticationController用于登录)

     

    自己代码实现:

    ShiroConfiguration


    package cn.com.rivercloud.secutity.config;

    import cn.com.rivercloud.secutity.filter.URLPathMatchingFilter;
    import cn.com.rivercloud.secutity.matcher.MyRetryLimitCredentialsMatcher;
    import cn.com.rivercloud.secutity.relam.SystemRelam;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.cache.MemoryConstrainedCacheManager;
    import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
    import org.apache.shiro.spring.LifecycleBeanPostProcessor;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.apache.shiro.mgt.SecurityManager;

    import javax.servlet.Filter;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;

    /**
    * author shichangle
    *
    */
    @Configuration
    public class ShiroConfiguration {

       /**
        * shiro生命周期处理器
        *
        * @return
        */
       @Bean
       public static LifecycleBeanPostProcessor getLifecycleBeanProcessor() {
           return new LifecycleBeanPostProcessor();
      }

       @Bean(name = "sessionDAO")
       public MemorySessionDAO getMemorySessionDao() {
           return new MemorySessionDAO();
      }

       @Bean(name = "simpleIdCookie")
       public SimpleCookie getSimpleCookie() {
           SimpleCookie simpleCookie = new SimpleCookie();
           simpleCookie.setName("SHIROSESSIONID");
           return simpleCookie;
      }

       //配置shirosession的一个管理器
       @Bean(name = "sessionManager")
       public DefaultWebSessionManager getDefaultWebSessionManager(@Qualifier("sessionDAO") MemorySessionDAO sessionDAO,
                                                                   @Qualifier("simpleIdCookie") SimpleCookie simpleCookie) {
           DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
           sessionManager.setSessionDAO(sessionDAO);
           sessionManager.setSessionIdCookie(simpleCookie);
           //设置session过期时间 1800s
           sessionManager.setGlobalSessionTimeout(1800000L);
           return sessionManager;
      }

       //配置session的缓存管理器
       @Bean(name = "shiroCacheManager")
       public MemoryConstrainedCacheManager getMemoryConstrainedCacheManager() {
           return new MemoryConstrainedCacheManager();
      }


       @Bean
       public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
           //安全事务管理器工厂类
           ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
           shiroFilterFactoryBean.setSecurityManager(securityManager);
           //shiroFilterFactoryBean.setLoginUrl("/index");//未登录时拦截的路径

           //配置访问权限
           Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
           //自定义个过滤器设置1
           Map<String, Filter> customizedFilter = new HashMap<>();
           //自定义过滤器配置2 ,命名,需要设置在过滤路径前

           //自定义过滤器设置4 启用
           customizedFilter.put("url", getURLPathMatchingFilter());
           shiroFilterFactoryBean.setFilters(customizedFilter);
           shiroFilterFactoryBean.setUnauthorizedUrl("/csm/api/v1.0/login");
           filterChainDefinitionMap.put("/**", "url");
           shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

           return shiroFilterFactoryBean;
      }

       public URLPathMatchingFilter getURLPathMatchingFilter() {
           return new URLPathMatchingFilter();
      }

       //配置核心安全事务管理器
       @Bean
       public SecurityManager securityManager(@Qualifier("shiroCacheManager") MemoryConstrainedCacheManager shiroCacheManager,
                                              @Qualifier("sessionManager") DefaultWebSessionManager sessionManager) {
           DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
           securityManager.setRealm(getRealm());
           securityManager.setRememberMeManager(rememberMeManager());
           securityManager.setCacheManager(shiroCacheManager);
           securityManager.setSessionManager(sessionManager);//设置过期时间

           return securityManager;
      }

       public CookieRememberMeManager rememberMeManager() {
           CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
           cookieRememberMeManager.setCookie(rememberMeCookie());
           cookieRememberMeManager.setCipherKey("EVANNIGHTLY_WAOU".getBytes());
           return cookieRememberMeManager;
      }

       @Bean
       public SimpleCookie rememberMeCookie() {
           SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
           simpleCookie.setMaxAge(86400);//存活 一天
           return simpleCookie;
      }

       //自定义权限登录器
       @Bean
       public SystemRelam getRealm() {
           SystemRelam systemRelam = new SystemRelam();
           systemRelam.setCredentialsMatcher(hashedCredentialsMatcher());
           return systemRelam;
      }


       @Bean
       public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
           AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
           authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
           return authorizationAttributeSourceAdvisor;
      }


       //以下用于免登陆
       @Bean(name = "myRetryLimitCredentialsMatcher")
       public MyRetryLimitCredentialsMatcher hashedCredentialsMatcher(){
           MyRetryLimitCredentialsMatcher hashedCredentialsMatcher = new MyRetryLimitCredentialsMatcher();
           // 采用MD5方式加密
           hashedCredentialsMatcher.setHashAlgorithmName("md5");
           // 设置加密次数
           hashedCredentialsMatcher.setHashIterations(2);
           return hashedCredentialsMatcher;
      }

    }



    URLPathMatchingFilter:

    package cn.com.rivercloud.secutity.filter;



    import cn.com.rivercloud.modal.system.User;
    import cn.com.rivercloud.secutity.domain.ResultData;
    import cn.com.rivercloud.user.service.PermissionService;
    import cn.com.rivercloud.user.service.RoleService;
    import cn.com.rivercloud.user.service.UserService;
    import cn.com.rivercloud.util.SpringContextUtils;
    import com.alibaba.fastjson.JSONObject;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.MemoryConstrainedCacheManager;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.session.mgt.eis.SessionDAO;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.PathMatchingFilter;
    import org.apache.shiro.web.servlet.ShiroHttpSession;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;


    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;


    /**
    * 所有接口前置过滤类
    * author shichangle
    * date 2019/12/18 0018 18:11
    */
    @SuppressWarnings("all")
    @Configuration
    public class URLPathMatchingFilter extends PathMatchingFilter {

       private static final Logger logger = LoggerFactory.getLogger(URLPathMatchingFilter.class);

       @Autowired
       private UserService userService;
       @Autowired
       private RoleService roleService;
       @Autowired
       private PermissionService permissionService;
       @Autowired
       private SessionDAO sessionDAO;
       @Autowired
       private SessionManager sessionManager;
       @Autowired
       private MemoryConstrainedCacheManager cacheManager;

       @Override
       protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {

           HttpServletRequest httpServletRequest = (HttpServletRequest) request;
           HttpServletResponse httpServletResponse = (HttpServletResponse) response;

           if (null == userService) {
               userService = SpringContextUtils.getContext().getBean(UserService.class);
          }
           if (null == roleService) {
               roleService = SpringContextUtils.getContext().getBean(RoleService.class);
          }
           if (null == permissionService) {
               permissionService = SpringContextUtils.getContext().getBean(PermissionService.class);
          }
           if (null == cacheManager) {
               cacheManager = SpringContextUtils.getContext().getBean(MemoryConstrainedCacheManager.class);
          }

           if (HttpMethod.OPTIONS.toString().equals((httpServletRequest).getMethod())) {
               httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
               return true;
          }
           String requestAPI = getPathWithinApplication(request);
           //指定接口放行,返回true
           //未放行的 判断登录   --未登录 返回登录页面
           //                   --登录了 判断该用户名对应的session 中的字段是否是新的
           //                             是 进行下一步
           //                             不是 进行重新登录


           //咱是放行
           if (containsApi(requestAPI)) {
               return true;
          }

           Subject subject = SecurityUtils.getSubject();
           //判断登录,没有登录,直接进行之后的流程
           if (!subject.isAuthenticated()) {
               //没有登录,重定向到指定页面
               logger.info("未登录:"+requestAPI);
               if (isAjax(request)) {
                   httpServletResponse.setCharacterEncoding("UTF-8");
                   httpServletResponse.setContentType("application/json");
                   ResultData resultData = new ResultData();
                   resultData.setCode(1);
                   resultData.setCode(403);
                   resultData.setMessage("您的登录状态已过期,请重新登录");
                   httpServletResponse.getWriter().write(JSONObject.toJSON(resultData).toString());
              } else {
                   //非ajax请求重定向为登录页面
                   httpServletResponse.sendRedirect("/index.html");
              }
               return false;
          } else {
               //登录
               Session session = subject.getSession();
               logger.debug("==session时间设置:" + String.valueOf(session.getTimeout())
                       + "===========");
               String id = subject.getPrincipal().toString();
               logger.debug("===当前用户id:==" + id);
               Serializable sessionId = session.getId();
               logger.debug("===当前用户sessionId:==" + sessionId);

               User byUsername = userService.findById(username);

               Cache<Object, Object> cache = cacheManager.getCache(id);
               ShiroHttpSession cacheSession = (ShiroHttpSession) cache.get(id);
               String kickout = cacheSession.getAttribute("kickout").toString();

               //对比数据
               if ("2".equals(kickout)) {
                   return true;
              } else {
                   //重新登录
                   logger.info("未登录:");
                   if (isAjax(request)) {
                       httpServletResponse.setCharacterEncoding("UTF-8");
                       httpServletResponse.setContentType("application/json");
                       ResultData resultData = new ResultData();
                       resultData.setCode(1);
                       resultData.setCode(403);
                       resultData.setMessage("您的登录状态已过期,请重新登录");
                       httpServletResponse.getWriter().write(JSONObject.toJSON(resultData).toString());
                  } else {
                       //非ajax请求重定向为登录页面
                       httpServletResponse.sendRedirect("/index.html");
                  }
                   return false;
              }
          }
      }


       private boolean containsApi(String requestApi) {

           List<String> likeList = new ArrayList<>();
           
           likeList.add("/static/");
           likeList.add("/css/");
           likeList.add("/img/");
           likeList.add("/js/");
           likeList.add("/loading/");
           likeList.add("/index.");
           likeList.add("/nav");
           likeList.add("/logo.");
           likeList.add("/avatar2.");

           for (String s : likeList) {
               if (requestApi.contains(s) ) {
                   return true;
              }
          }
           for (String s : allList) {
               if (requestApi.equals(s) ) {
                   return true;
              }
          }
           return false;
      }

       private boolean isAjax(ServletRequest request) {
           String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
           if ("XMLHttpRequest".equalsIgnoreCase(header)) {
               return false;
          }
           return true;
      }
    }


    macher(重写,用于免密登录)
    package cn.com.rivercloud.secutity.matcher;

    import cn.com.rivercloud.secutity.token.LoginType;
    import cn.com.rivercloud.secutity.token.UsernamePasswordLoginTypeToken;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.springframework.context.annotation.Configuration;

    /**
    * author shichangle
    * date 2020/5/21 0021 15:08
    */
    @Configuration
    public class MyRetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
       @Override
       public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
           UsernamePasswordLoginTypeToken tk = null;
           try {
               tk = (UsernamePasswordLoginTypeToken) token;
          } catch (Exception e) {
               return super.doCredentialsMatch(token, info);
          }
           if(tk.getLoginType().equals(LoginType.NOPASSWD)){
               return true;
          }
           return super.doCredentialsMatch(token, info);
      }
    }



    relam:(创建的时候名称有点问题)
    package cn.com.rivercloud.secutity.relam;

    import cn.com.rivercloud.modal.system.User;
    import cn.com.rivercloud.user.service.UserService;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    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.util.ByteSource;
    import org.springframework.beans.factory.annotation.Autowired;

    /**
    * author shichangle
    * date 2019/12/18 0018 11:27
    */
    public class SystemRelam extends AuthorizingRealm {

       @Autowired
       private UserService userService;

       //获取授权信息方法
       @Override
       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
           SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
           return s;
      }

       //获取认证信息,根据token获取用户名
       @Override
       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
           String username = token.getPrincipal().toString();
           User map  = userService.findById(username);
           String passwordInDB = map.getPassword();
           String salt = map.getSalt();
           SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,passwordInDB, ByteSource.Util.bytes(salt),getName());
           return authenticationInfo;
      }
    }

     

    token:用于免密登录

    package cn.com.rivercloud.secutity.token;

    /**
    * 免密登录标识
    * author shichangle
    * date 2020/5/21 0021 14:56
    */
    public enum LoginType {
      PASSWORD("password"), // 密码登录
      NOPASSWD("nopassword"); // 免密登录

      private String code;// 状态值

      private LoginType(String code) {
          this.code = code;
      }
      public String getCode () {
          return code;
      }
    }


    package cn.com.rivercloud.secutity.token;

    import org.apache.shiro.authc.UsernamePasswordToken;

    /**
    * UsernamePasswordToken子类,用于免登陆
    * author shichangle
    * date 2020/5/21 0021 14:47
    */
    public class UsernamePasswordLoginTypeToken extends UsernamePasswordToken {

      private LoginType loginType;

      public UsernamePasswordLoginTypeToken() {
          super();
      }

      public UsernamePasswordLoginTypeToken(String username, String password, boolean rememberMe, String host, LoginType loginType) {
          super(username, password, rememberMe, host);
          this.loginType = loginType;
      }

      /**
        * 免密登录
        * @param username
        */
      public UsernamePasswordLoginTypeToken(String username){
          super(username, "", false, null);
          this.loginType = LoginType.NOPASSWD;
      }

      /**
        * 密码登录
        * @param username
        * @param pwd
        */
      public UsernamePasswordLoginTypeToken(String username, String pwd) {
          super(username, pwd, false, null);
          this.loginType = LoginType.PASSWORD;
      }

      public LoginType getLoginType() {
          return loginType;
      }

      public void setLoginType(LoginType loginType) {
          this.loginType = loginType;
      }
    }

     

    4、踢出用户的实现

    实现原理:

    1、登录成功后,将用户信息存入shiro的session中,设置指定状态
    2、踢出用户,去缓存中查找指定用户信息,更改状态
    3、过滤接口,每次请求都检查session中的信息状态,登录则放行,未登录则返回登录页面
    1//登录信息存入缓存中
    subject.login(token);
    //登录成功,更改用户的登录状态,以及去掉第一次登录的标识
    userService.updateLoginStatus(serviceByUsername.getId(), LOGINSTATUS);
    User byUsername = userService.findById(serviceByUsername.getId());
    byUsername.setPassword("");
    byUsername.setSalt("");
    logger.info(username + "登录成功");
    User.setUser(session, serviceByUsername);

    //登录信息存入缓存中
    Session subsession = subject.getSession();
    logger.debug("==session时间设置:" + String.valueOf(subsession.getTimeout())
                       + "===========");
    String id = subject.getPrincipal().toString();
    logger.debug("===当前用户id:==" + id);
    Serializable sessionId = subsession.getId();
    logger.debug("===当前用户sessionId:==" + sessionId);

    subsession.setAttribute("kickout", "2");
    Cache<Object, Object> cache = cacheManager.getCache(id);
    cache.put(id, session);
    return jsonSuccess(byUsername, "登录成功");

     

    2、踢出用户

    /**
     * 踢出用户
     *
     * @param sessionId
     */
       
     Autowired
     private MemoryConstrainedCacheManager cacheManager;
         
     @Override
     public void kinckUser(HttpSession session, String sessionId) {
         getSessionBysessionId(sessionId);
    }

     //根据sesisonid获取单个session对象
     private void getSessionBysessionId(String sessionId) {
         Cache<Object, Object> test = cacheManager.getCache(sessionId);
         ShiroHttpSession cacheSession = (ShiroHttpSession) test.get(sessionId);
         Object kickout = cacheSession.getAttribute("kickout");
         System.out.println(kickout);
         cacheSession.setAttribute("kickout", "1");
    }

     

    3、在URLPathMatchingFilter中有对每个接口进行过滤验证。

     

     

    免登陆接口:

     public String nologin(HttpSession session, @RequestBody User user) throws Exception {

           String username = user.getUsername();
           String companyName = user.getCompanyName();
           username = HtmlUtils.htmlEscape(username);

           Subject subject = SecurityUtils.getSubject();
           UsernamePasswordLoginTypeToken tk = new UsernamePasswordLoginTypeToken(username);

           tk.setRememberMe(true);

           try {
               subject.login(tks);
               //登录成功,更改用户的登录状态,以及去掉第一次登录的标识
               userService.updateLoginStatus(username, LOGINSTATUS);
               User byUsername = userService.findById(username);
               byUsername.setPassword("");
               byUsername.setSalt("");
               logger.info(username + "登录成功");
               User.setUser(session, user);
               //登录信息存入缓存中

               Session subsession = subject.getSession();
               logger.debug("==session时间设置:" + String.valueOf(subsession.getTimeout())
                       + "===========");
               String id = subject.getPrincipal().toString();
               logger.debug("===当前用户id:==" + id);
               Serializable sessionId = subsession.getId();
               logger.debug("===当前用户sessionId:==" + sessionId);

               subsession.setAttribute("kickout", "2");
               Cache<Object, Object> cache = cacheManager.getCache(id);
               cache.put(id, session);
               return jsonSuccess( "登录成功");
          } catch (AuthenticationException e) {

               return jsonFailure();
          }
      }

     5、设置session过期时间

    SecurityUtils.getSubject().getSession().setTimeout(l);
    注意,单位是毫秒
  • 相关阅读:
    现在SimpleMemory的CSS(by BNDong)
    I AK IOI
    最大半联通子图
    曾经SimpleMemory的CSS
    幼儿园战争
    炸掉卡西欧991CNX
    LuoguP1131选择客栈
    2019CSP-S2养成任务
    NOIP2013&NOIP2018&USACO 三倍经验铺路题巧妙解法
    NOIP2018D2T1 旅行
  • 原文地址:https://www.cnblogs.com/notchangeworld/p/12941820.html
Copyright © 2011-2022 走看看