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;

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM