1、ldap登陸html
ldap相關的文檔請參考其餘資料,再次再也不詳述。假設ldap已經存儲了用戶的相關信息數據;目前須要再其餘服務中調用ldap的用戶信息來登陸。以下前端
1.控制器層web
@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登陸代碼;@Serviceredis
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權限驗證spring
/** * 生成簽名,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; } }
2、shiro相關配置數據庫
shiro配置文件以下,將CustomRealm注入DefaultWebSecurityManager。apache
@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用戶的方法以下:api
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; } }