下面是常见的前后端分离的架构,准确的说,这是一种半分离的架构,这种架构存在一些问题:
1,SEO,搜索引擎只能识别静态的HTML资源,爬HTML的DOM树,它是没法识别js动态渲染出来的内容的,所以如果是这种架构,SEO会有问题
2,页面渲染是在浏览器完成的,如果业务复杂,要数据量大,可能会对浏览器造成压力,可能会卡顿。
鉴于以上前后端分离的架构存在的问题,我们要搭的一个前后端分离的架构是下面的架构:
主要区别是把Nginx换成了nodeJS,nodejs可以实现nginx的所有的功能。
而且还提供了其他的功能,比如服务端的页面渲染,浏览器在请求一个html资源的时候,nodejs可以发api请求,在nodejs上把页面渲染好,然后把渲染好的页面给浏览器,此时做SEO的时候,搜索引擎爬到的是一个完整的页面。如果某些页面不需要SEO,仍然可以在页面直接发api,在浏览器渲染页面,这两种都支持。
本实验用springboot代替nodejs,页面使用html js。之前的系列文章里,都是使用的postman作为客户端应用的,从今往后,就用实际的前端应用替换postman了。
整体就是下面这种架构:
下面开始敲代码
1,新建一个应用nb-admin
效果:
未登录时候,显示
点击登录,输入用户名密码
(认证服务器写死的,密码是123456即可)
登录后:
主要代码:在 AdminController ,处理登录请求,调用网关,获取token ,获取后放在了 前端服务器的session里
@ResponseBody @PostMapping("/login") public void login(@RequestParam String username,@RequestParam String password/*@RequestBody Credential credential*/, HttpSession session){ //认证服务器验token地址 /oauth/check_token 是 spring .security.oauth2的验token端点 String oauthServiceUrl = "http://localhost:9070/token/oauth/token"; HttpHeaders headers = new HttpHeaders();//org.springframework.http.HttpHeaders headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//不是json请求 //网关的appId,appSecret,需要在数据库oauth_client_details注册 headers.setBasicAuth("admin","123456"); MultiValueMapparams = new LinkedMultiValueMap<>(); params.add("username",username); params.add("password",password); params.add("grant_type","password"); params.add("scope","read write"); HttpEntity > entity = new HttpEntity<>(params,headers); ResponseEntity response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, AccessToken.class); session.setAttribute("token",response.getBody()); log.info("token info : {}",response.getBody().toString()); }
在 index.html 里,进页面就发一个/me 请求,如果能从session获取到token信息(先这么干),说明登录成功:
@GetMapping("/me") @ResponseBody public AccessToken me(HttpSession session){ AccessToken accessToken = (AccessToken) session.getAttribute("token"); return accessToken; }
index.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Indextitle>
head>
<body>
<h1>欢迎来到sso子系统1h1>
<div id="loginTip">div>
<p><button onclick="getOrderInfo()">获取订单信息button>p>
<table>
<tr><td>order idtd><td><input id="orderId" />td>tr>
<tr><td>order product idtd><td><input id="productId" />td>tr>
table>
body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js">script>
<script>
function getOrderInfo(){
$.get("api/order/orders/1",function(data){
$("#orderId").val(data.id);
$("#productId").val(data.productId);
});
}
$(document).ready(function(){
$.get("/me",function(data,status){
if(data){
//已登录
var htm = "已登录,退出";
$("#loginTip").html(htm);
}else{
//未登录
var href = "未登录,去登录";
$("#loginTip").append(href);
}
});
});
script>
html>
login.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>logintitle>
head>
<body>
<center>
<h3>登录h3>
<table border="1">
<tr>
<td>用户名td>
<td><input id="username" name="username" value="lhy"/>td>
tr>
<tr>
<td>密码td>
<td><input id="password" name="password" value="123456"/>td>
tr>
<tr>
<td colspan="2" align="right"><button id="submitBtn" onclick="login()">登录button>td>
tr>
table>
center>
body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js">script>
<script>
function login() {
var username = $("#username").val();
var password = $("#password").val();
//alert(username+", " +password)
$.ajax({
type: "POST",
url: "/login",
data: {"username":username, "password":password},
success: function(msg){
location.href = "/index";
},
error:function(msg){
alert("登录失败!!")
}
});
}
script>
html>
请求转发
在nb-admin上,index.html,加上获取订单信息,点击获取订单按钮,发一个请求到前端服务nb-admin,nb-admin配上zuul网关,让它把这个请求转发到网关去,由网关再去请求订单服务,获取订单信息。
在nb-admin项目加入zuul依赖:
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-zuulartifactId> dependency>
启动类加上注解:
@EnableZuulProxy
配置zuul路由,以 /api 开头的请求,转发到网关
zuul: routes: #路由的配置是个Map,可以配置多个 api: #api开头的请求,都转发到网关9070 url: http://gateway.nb.com:9070 sensitive-headers: null #设置敏感头设置为空,Authorization等请求头的请求,都往后转发 server: port: 8080 spring: application: name: admin
注:几个项目都配置了host,以方便区分cookie等
127.0.0.1 gateway.nb.com ----网关
127.0.0.1 admin.nb.com ----前端服务
127.0.0.1 auth.nb.com ----认证服务
127.0.0.1 order.nb.com ----订单服务
网关上,获取订单信息,是需要token的,所以在nb-admin上,需要从session中获取到token,加在请求头里,再发往网关。新建一个zuulFilter,统一在请求头加token。
SessionTokenFilter
package com.nb.security.admin; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 从session获取token,统一加到请求头中去 */ @Component public class SessionTokenFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); AccessToken accessToken = (AccessToken)request.getSession().getAttribute("token"); if(accessToken != null){ requestContext.addZuulRequestHeader("Authorization","Bearer "+accessToken.getAccess_token()); } return null; } }
AccessToken
import lombok.Data; import java.util.Date; /** * access_token * Created by: 李浩洋 on 2020-01-02 **/ @Data public class AccessToken { private String access_token; private String token_type; private Date expires_in; private String scope; }
现在效果:
登录:
登录后
点击获取订单信息,会发一个 api/order/orders/1 请求,由于nb-admin里配置了zuul路由,api开头的请求都会转发到网关,所以这里实际访问了网关,网关又转发到了order-api,最终返回orderInfo
function getOrderInfo(){ $.get("api/order/orders/1",function(data){ $("#orderId").val(data.id); $("#productId").val(data.productId); }); }
退出登录
退出登录很简单,直接发一个请求,将nb-admin里的session失效,就行了
@GetMapping("/logout") public String logout(HttpSession session){ session.invalidate(); return "index"; }
目前已经用 springboot实现了一个前端服务器,实现了oauth协议中 password模式的登录,请求转发,退出。目前还存在不少问题,下节讲解问题并解决。
(其实我认为将这个就当做某个系统的后端服务也没啥问题,类似于你去微信申请客户端应用一样,这里的nb-admin就是我们自己的公众号项目)
代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-5-1-ui
如果对你有一点,就给个小星星吧