一、说明二、代码实现三、基本组件完整架构实现四、zuul+Shiro实现
4.1、Shiro基本集成4.2、Shiro基本集成---身份验证4.3、Shiro基本集成---权限验证
一、说明Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分。
引用jar包
org.springframework.cloud spring-cloud-starter-netflix-zuul
application.properties配置
server.port=8081 spring.application.name=zuul eureka.client.service-url.defaultZone= http://localhost:8083/eureka/ #zuul #以eurekaClient2开头的请求都转发到eurekaClient2 zuul.routes.api-a.path=/eurekaClient1 @Override public String filterType() { return "pre"; } @Override public int filterOrder() { //数字越小表示顺序越高,越先执行 return 0; } @Override public boolean shouldFilter() { return true; //表示是否需要执行该filter,true表示执行,false表示不执行 } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); // System.out.println(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); String accessToken = request.getParameter("token"); // 获取请求的参数 if(StringUtils.isNotBlank(accessToken)) { //进行路由 ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); ctx.set("isSuccess", true); }else{ //不进行路由 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(400); ctx.setResponseBody("token is empty"); ctx.set("isSuccess", false); } return null; } }
没带token参数时返回
带token参数时正常访问后面的系统服务
现在有Eureka Server,端口8083;
有Zuul【zuul】,端口8081;
有Eureka Client【EurekaClient2】,开启分布式,端口分别是7081、7082;
有Eureka Client【EurekaClient1】,端口分别是7080;
现在有EurekaClient2服务可以直接访问接口:http://localhost:7081/getPersonById.do?id=1
现在有EurekaClient1服务可以直接访问接口【这个接口会通过ribbon负载均衡调用EurekaClient2服务----ribbon的实现】:
http://localhost:7080/ribbonPersonById.do
现在通过zuul服务来路由转发:
http://localhost:8081/eurekaClient1/ribbonPersonById.do
http://localhost:8081/EurekaClient2/getPersonById.do?id=1
综合结论:
从上面可以看出,单独的Eureka Client也是可以进行访问的,另外Eureka Client之间可以通过ribbon实现调用。
当有zuul组件时,任何请求都是通过zuul组件所在服务系统进行路由转发到对应Eureka Client上。
因此,上面的请求也反映了zuul组件的功能。
从上面的简单实例,一个完整的系统平台,相关请求都是经过zuul来进行转发路由,因此在实际中“登录鉴权管理”也应该在zuul服务系统上进行构建,这里就介绍zuul+Shiro实现。
4.1、Shiro基本集成引用jar包
org.apache.shiro shiro-spring1.4.0
Shiro的config配置类
@Configuration public class ShiroConfig { @Bean public MyShiroRealm customRealm() { //用户数据和Shiro数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。 MyShiroRealm customRealm = new MyShiroRealm(); return customRealm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(customRealm()); return defaultSecurityManager; } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 factoryBean.setLoginUrl("/login"); //这是当任何url时,都进入/login界面 // 登录成功后要跳转的连接 factoryBean.setSuccessUrl("/index"); //当验证成功后,会进入/index //未授权界面、权限不足跳转页面,跳转到403 factoryBean.setUnauthorizedUrl("/403"); //对应MyShiroRealm类的返回null时,自动跳转到/403 //加载ShiroFilter权限控制规则 loadShiroFilterChain(factoryBean); return factoryBean; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean) { MapfilterChainMap = new linkedHashMap (); filterChainMap.put("/login", "anon"); // filterChainMap.put("/register", "anon"); //配置退出 过滤器 filterChainMap.put("/logout", "logout"); //剩余请求需要身份认证 filterChainMap.put(" @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //查询mysql获取用户信息 通过浏览器提交的用户名去数据库查找相应的用户信息 UserPo userInfo = userInfoService.selectUser(token.getUsername()); if(userInfo != null){ SecurityUtils.getSubject().getSession().setAttribute("user", userInfo); // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 String userloginName= userInfo.getUserName(); String userPasswd=userInfo.getPassWord(); String name=getName(); //realm name return new SimpleAuthenticationInfo(userloginName, userPasswd, name); } return null; } }
------从上面代码看出,通过传入的用户名称去查DB,然后和传来的登录密码对比,相同就说明身份验证通过。
模拟测试
@Controller public class ShiroController { // @Autowired // private SessionDAO sessionDao; @RequestMapping(value = "/login", method = RequestMethod.GET) public String loginForm(Model model) { model.addAttribute("user", new UserPo()); return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(UserPo user) { String username = user.getUserName(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassWord(), false); // 获取当前的Subject, 代表当前正在执行 *** 作的用户 Subject currentUser = SecurityUtils.getSubject(); try { currentUser.login(token); System.out.println("对用户[" + username + "]进行登录验证..验证通过"); } catch (UnknownAccountException uae) { System.out.println("对用户[" + username + "]进行登录验证..验证未通过,未知账户"); } catch (IncorrectCredentialsException ice) { System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证"); } catch (LockedAccountException lae) { System.out.println("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定"); } catch (ExcessiveAttemptsException eae) { System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多"); } catch (AuthenticationException ae) { // 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 System.out.println("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); } // 验证是否登录成功 if (currentUser.isAuthenticated()) { System.out.println("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化 *** 作)"); UserPo user2 = (UserPo) SecurityUtils.getSubject().getSession().getAttribute("user"); return "index"; } else { token.clear(); return "login"; } }
首先,任意没登录请求来访问都是跳转到登录页面
http://localhost:8081/login
登录成功后,跳转到成功页面。那么,后面直接访问zuul服务的接口都正常访问。
这里zuul主要是做路由转发的,那么事先没登录的访问去访问EurekaClient1,也是先跳转Zuul服务的登录系统,然后接口才能访问。
前面已经说明,当符合身份登录的继续访问,但是不同用户身份的权限又不一样,可以通过不同权限来解决请求是否访问成功。
首先前面Shiro配置类有lifecycleBeanPostProcessor()方法是开启“Shiro权限注解”,那么可以通过注解方法来确定不同类或方法的权限。
还是MyShiroRealm 类增加如下配置内容。
public class MyShiroRealm extends AuthorizingRealm { @Autowired UserInfoServiceImpl userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String loginName = (String) super.getAvailablePrincipal(principalCollection); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //查询mysql获取用户信息 UserPo userInfo = userInfoService.selectUser(loginName); if(userInfo != null){ info.setStringPermissions(userInfoService.userPerm(loginName));//用户对应的所有权限 SecurityUtils.getSubject().getSession().setAttribute("user", userInfo); } return info; }
分析:userInfoService.userPerm(loginName)方法就是查数据库来获取这个登录用户所赋给的权限。
模拟测试,假设给用户设置user:show、user:admin两个权限,那么
@Override public SetuserPerm(String userName) { Set permSet = new HashSet<>(); //配置的权限,模拟查库 permSet.add("user:show"); permSet.add("user:admin"); return permSet; }
为两个PersonController类、HelloWorldController类配置权限
@Controller @RequestMapping("/basic") @RequiresPermissions("user:admin") //这个类下面的方法拥有的权限 public class HelloWorldController { //http://localhost:8084/basic/sayHello.do @RequestMapping("/sayHello.do") @ResponseBody //这是返回json格式 public Object sayHello() { return "Hello,World!"; } @RequestMapping("/wrong.html") public Object wrongPage(){ return "wrong"; } }
@Controller @RequestMapping("/person") @RequiresPermissions("user:person") public class PersonController { @Autowired private PersonServiceImpl personServiceImpl; //http://localhost:8084/person/getPersonById.do @RequestMapping("/getPersonById.do") @ResponseBody public Object getPersonById(){ PersonPo person = personServiceImpl.getPersonById(1); return JSON.toJSONString(person); } }
直接请求的效果
以上就基本完成Zuul整合Shiro的功能。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)