ldap结合shiro搭建统一登录平台
Posted yuluoxingkong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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; } }
以上是关于ldap结合shiro搭建统一登录平台的主要内容,如果未能解决你的问题,请参考以下文章