• ldap结合shiro搭建统一登录平台


    一、ldap登录

    ldap相关的文档请参考其他资料,再次不再详述。假设ldap已经存储了用户的相关信息数据;目前需要再其他服务中调用ldap的用户信息来登录。如下

    1.控制器层

     @ApiOperation(value = "登录", notes = "", httpMethod = "GET")
        @RequestMapping(value = "/login",method = RequestMethod.GET)
        public ResponseBean findByCn(@RequestParam(required = true) String uid,
                                     @RequestParam(required = true) String userPassword) throws InvalidNameException, CetcBigDataException {
            return  new ResponseBean(ErrorCode.SUCCESS.getCode(),ErrorCode.SUCCESS.getMsg(), ldapService.login(uid,userPassword));
    
        }

    2.ldap登录代码;@Service

    public class LdapService {
    
        @Autowired
        private LdapTemplate ldapTemplate;
        @Autowired
        private JWTUtil jwtUtil;
    
        @Value("${spring.ldap.urls}")
        private String url;
    
        @Value("${spring.ldap.base}")
        private String basedn;
    
        @Value("${spring.ldap.domain}")
        private String domain;
    
        private Hashtable<String, String> env = new Hashtable<String, String>();
    
        public ActiveUser login(String uid, String userPassword) throws CetcBigDataException {
            boolean loginFlag=ldapAuth(uid,userPassword);
         //   boolean loginFlag=connect(uid,userPassword);
    
            if (!loginFlag){
                throw new CetcBigDataException("登录失败");
            }
    
            ActiveUser person=new ActiveUser();
            person.setUserCode(uid);
    
            person.setToken(jwtUtil.sign(uid,userPassword));
    
            return person;
    
        }
        /**
         * 方法一:AD认证
         *
         * @param username 用户名
         * @param password 密码
         */
        boolean ldapAuth(String username, String password) {
            EqualsFilter filter = new EqualsFilter("uid", username);
            return ldapTemplate.authenticate("", filter.toString(), password);
        }
      /**
      * 方法二,
    AD认证
      */
    public boolean connect(String userName,String passwd) {
            boolean result=false;
            LdapContext ldapContext = null;
            //用户名称,cn,ou,dc 分别:用户,组,域
            env.put(Context.SECURITY_PRINCIPAL, "uid="+userName+","+domain+","+basedn);
            //用户密码 cn 的密码
            env.put(Context.SECURITY_CREDENTIALS, passwd);
            //url 格式:协议://ip:端口/组,域   ,直接连接到域或者组上面
            env.put(Context.PROVIDER_URL, url+"/"+basedn);
            //LDAP 工厂
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            //验证的类型     "none", "simple", "strong"
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            try {
                ldapContext = new InitialLdapContext(env, null);
                result=true;
                System.out.println("---connection is ready----");
            } catch (NamingException e) {
                System.out.println("--- get connection failure ----");
            }
            return result;
        }
    }

    3.登录时,需要生成token。用于shiro权限验证

     /**
         * 生成签名,24小时后过期
         * @param username 用户名
         * @param secret 用户的密码
         * @return 加密的token
         */
        public  String sign(String username, String secret) {
            try {
                Date date = new Date(System.currentTimeMillis() + TOKEN_EXPIRE_TIME);
                Algorithm algorithm = Algorithm.HMAC256(secret);
                // 附带username信息
                String token =  JWT.create()
                        .withClaim("username", username)
                        .withExpiresAt(date)
                        .sign(algorithm);
                redisTemplate.opsForValue().set("token::"+username,token);
                redisTemplate.expire("token:"+username,REDIS_TOKEN_EXPIRE_TIME, TimeUnit.DAYS);
                return token;
            } catch (Exception e) {
                LOG.error("签名失败 {}",username);
                return null;
            }
        }

    二、shiro相关配置

    shiro配置文件如下,将CustomRealm注入DefaultWebSecurityManager。

    @Configuration
    public class ShiroConfig {
    
        @Bean("securityManager")
        public DefaultWebSecurityManager getManager(CustomRealm realm, @Value("${spring.redis.host}") String host,
                                                    @Value("${spring.redis.port}") Integer port) {
            DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
            // 使用自己的realm
            manager.setRealm(realm);
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            // 自定义缓存实现 使用redis
            manager.setCacheManager(cacheManager(host, port));
            // 自定义session管理 使用redis
            manager.setSessionManager(sessionManager(host, port));
    
            manager.setSubjectDAO(subjectDAO);
            return manager;
        }
    
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
            // 添加自己的过滤器并且取名为jwt
            Map<String, Filter> filterMap = new HashMap<>();
            filterMap.put("jwt", new JWTFilter());
            factoryBean.setFilters(filterMap);
            factoryBean.setSecurityManager(securityManager);
            factoryBean.setUnauthorizedUrl("/401");
            /*
             * 自定义url规则 http://shiro.apache.org/web.html#urls-
             */
            Map<String, String> filterRuleMap = new HashMap<>(16);
            // 放行swagger
            filterRuleMap.put("/swagger-ui.html", "anon");
            filterRuleMap.put("/swagger-resources", "anon");
            filterRuleMap.put("/v2/api-docs", "anon");
            filterRuleMap.put("/webjars/springfox-swagger-ui/**", "anon");
            filterRuleMap.put("/wechatLogin", "anon");
            filterRuleMap.put("/wechatInfo", "anon");
            filterRuleMap.put("/get-message", "anon");
            filterRuleMap.put("/get-appMessage", "anon");
            filterRuleMap.put("/label-get-token", "anon");
            // 访问401和404页面不通过我们的Filter
            filterRuleMap.put("/401", "anon");
            filterRuleMap.put("/**", "jwt");
            factoryBean.setFilterChainDefinitionMap(filterRuleMap);
            return factoryBean;
        }
    
        /**
         * Shiro生命周期处理器
         */
        @Bean
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        /**
         * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
         * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
         */
        @Bean
        @DependsOn("lifecycleBeanPostProcessor")
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            // 强制使用cglib,防止重复代理和可能引起代理出错的问题
            // https://zhuanlan.zhihu.com/p/29161098
            defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
            return defaultAdvisorAutoProxyCreator;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
                DefaultWebSecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    
        /**
         * cacheManager 缓存 redis实现 使用的是shiro-redis开源插件
         *
         * @return
         */
        public RedisCacheManager cacheManager(String host, Integer port) {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager(host, port));
            return redisCacheManager;
        }
    
        /**
         * 配置shiro redisManager 使用的是shiro-redis开源插件
         *
         * @return
         */
        public RedisManager redisManager(String host, Integer port) {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host);
            redisManager.setPort(port);
            // 配置缓存过期时间
            redisManager.setExpire(1800);
            return redisManager;
        }
    
        /**
         * Session Manager 使用的是shiro-redis开源插件
         */
        @Bean
        public DefaultWebSessionManager sessionManager(String host, Integer port) {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            sessionManager.setSessionDAO(redisSessionDAO(host, port));
            return sessionManager;
        }
    
        /**
         * RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件
         */
        @Bean
        public RedisSessionDAO redisSessionDAO(String host, Integer port) {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager(host, port));
            return redisSessionDAO;
        }
    CustomRealm主要用于接口验证用户正确与否,需要和shiro相关注解搭配使用,如下realm配置好后,控制器接口加入注解@RequiresAuthentication 就会对token进行验证
    */
    @Component
    public class CustomRealm  extends AuthorizingRealm {
        private final Logger LOG = LoggerFactory.getLogger(CustomRealm.class) ;
        @Lazy
        @Autowired
        private LdapService ldapService;
    
        @Lazy
        @Autowired
        private RedisTemplate redisTemplate;
        /**
         * 必须重写此方法,不然Shiro会报错
         */
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof JWTToken;
        }
    
        /**
         * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = JWTUtil.getUsername(principals.toString());
            String getPrimaryPrincipal=(String) principals.getPrimaryPrincipal();
            LOG.info(getPrimaryPrincipal);
            LOG.info(JWTUtil.getUsername(getPrimaryPrincipal));
    
            Person user = ldapService.findByUid(username);
    
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    
            // Set<String> permission = new HashSet<>(Arrays.asList(user.getPermission().split(",")));
            // simpleAuthorizationInfo.addStringPermissions(permission);
            // redisTemplate.opsForValue().getAndSet("user",user);
            return simpleAuthorizationInfo;
        }
    
        /**
         * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth)  throws AuthenticationException {
    
            //前端传过来的token
            String token = (String) auth.getCredentials();
            // 解密获得username,用于和数据库进行对比
            String username = JWTUtil.getUsername(token);
            if (username == null) {
                // return null;
                LOG.warn("token invalid");
                throw new IncorrectCredentialsException();
            }
            Person userBean = ldapService.findByUid(username);
            if (userBean == null) {
                LOG.warn("User {} didn't existed!",username);
                throw new UnknownAccountException("User didn't existed!");
            }
            try {
                JWTUtil.verifyEx(token, username ,userBean.getUserPassword());
            }catch (Exception e) {
                if (e instanceof TokenExpiredException){
                    Boolean tokenFlag = redisTemplate.hasKey("token:" + username);
                    if (!tokenFlag){
                        throw new AuthenticationException("Authorization 过期");
                    }
                }else{
                    LOG.warn("Username  {} or password error",username);
                    throw new AuthenticationException("token error");
                }
            }
            return new SimpleAuthenticationInfo(token, token, "my_realm");
        }
    
    }
     @ApiOperation(value = "查询所有person", notes = "", httpMethod = "GET")
        @RequestMapping(value = "/findAll",method = RequestMethod.GET)
        @RequiresAuthentication
        public ResponseBean findAll(HttpServletRequest request) throws InvalidNameException {
            String token = request.getHeader("Authorization");
            String userCode = JWTUtil.getUsername(token);
            if (userCode == null) {
                return new ResponseBean(ErrorCode.UNAUTHORIZED.getCode(), ErrorCode.UNAUTHORIZED.getMsg(),  null);
            }
    
            return  new ResponseBean(ErrorCode.SUCCESS.getCode(),ErrorCode.SUCCESS.getMsg(), ldapService.getAllPersonNames());
    
        }

    查询ldap用户的方法如下:

      public Person findByUid(String uid)  {
    
            List<Person>  personList= ldapTemplate.search((LdapQuery) query().where("uid").is(uid),new PersonAttributesMapper());
            if (CollectionUtils.isEmpty(personList)){
                return null;
            }
    
            Person person=personList.get(0);
    
            return person;
        }
    PersonAttributesMapper代码如下:

    public class PersonAttributesMapper  implements AttributesMapper<Person> {
    
    
        @Override
        public Person mapFromAttributes(Attributes attrs) throws NamingException {
            Person person = new Person();
            person.setSuerName((String) attrs.get("sn").get());
            person.setCommonName((String) attrs.get("cn").get());
            person.setUid((String) attrs.get("uid").get());
            byte[] bytes= (byte[]) attrs.get("userPassword").get();
    
            person.setUserPassword(new String(bytes));
    
            return person;
    
        }
    }
  • 相关阅读:
    Number Two
    蝴蝶结
    webug3.0靶场渗透基础Day_1
    SQL SERVER2014的安装
    SQLILABS学习笔记(一)
    关于暴力破解的一些学习笔记(pikachu)
    sql注入学习笔记
    CSRF与平行越权的区别
    任意文件下载(pikachu)
    XSS跨站脚本攻击学习笔记(pikachu)
  • 原文地址:https://www.cnblogs.com/yuluoxingkong/p/12202439.html
Copyright © 2020-2023  润新知