webapp用户身份认证方案 JSON WEB TOKEN 实现Deme示例,Java版

本项目依赖于下面jar包:

  • nimbus-jose-jwt-4.13.1.jar (一款开源的成熟的JSON WEB TOKEN 解决方法,本仓库的代码是对其的进一步封装)
  • json-smart-2.0-RC2.jar和asm-1.0-RC1.jar (依赖jar包,主要用于JSONObject序列化)
  • cors-filter-2.2.1.jar和java-property-utils-1.9.1.jar(用于处理跨域ajax请求)
  • junit.jar(单元测试相关jar包)

核心类Jwt.java结构:

2个静态方法createToken和validToken,分别用于生成TOKEN和校验TOKEN; 定义了枚举TokenState,用于表示验证token时的结果,用户可根据结果进行不同处理:

  • EXPIRED token过期
  • INVALID token无效(包括token不合法,token格式不对,校验时异常)
  • VALID token有效

使用示例

获取token

Map<String , Object> payload=new HashMap<String, Object>();
Date date=new Date();
payload.put("uid", "");//用户id
payload.put("iat", date.getTime());//生成时间
payload.put("ext",date.getTime()+**);//过期时间1小时
String token=Jwt.createToken(payload);
System.out.println("token:"+token);

校验token

String token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIyOTE5Njk0NTIiLCJpYXQiOjE0NjA0MzE4ODk2OTgsImV4dCI6MTQ2MDQzNTQ4OTY5OH0.RAa71BnklRMPyPhYBbxsfJdtXBnXeWevxcXLlwC2PrY";
Map<String, Object> result=Jwt.validToken(token); String state=(String)result.get("state");
switch (TokenState.getTokenState(state)) {
case VALID:
//To do somethings
System.out.println("有效token");
break;
case EXPIRED:
System.out.println("过期token");
break;
case INVALID:
System.out.println("无效的token");
break;
} System.out.println("返回结果数据是:" +result.toString());

 

项目应用中代码:

JAT 工具类

public class Jwt {

    /**
* 秘钥
*/
private static final byte[] SECRET="3d990d2276917dfac04467df11fff26d".getBytes(); /**
* 初始化head部分的数据为
* {
* "alg":"HS256",
* "type":"JWT"
* }
*/
private static final JWSHeader header=new JWSHeader(JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null, null, null, null, null, null, null, null, null); /**
* 生成token,该方法只在用户登录成功后调用
*
* @param Map集合,可以存储用户id,token生成时间,token过期时间等自定义字段
* @return token字符串,若失败则返回null
*/
public static String createToken(Map<String, Object> payload) {
String tokenString=null;
// 创建一个 JWS object
JWSObject jwsObject = new JWSObject(header, new Payload(new JSONObject(payload)));
try {
// 将jwsObject 进行HMAC签名
jwsObject.sign(new MACSigner(SECRET));
tokenString=jwsObject.serialize();
} catch (JOSEException e) {
System.err.println("签名失败:" + e.getMessage());
e.printStackTrace();
}
return tokenString;
} /**
* 校验token是否合法,返回Map集合,集合中主要包含 state状态码 data鉴权成功后从token中提取的数据
* 该方法在过滤器中调用,每次请求API时都校验
* @param token
* @return Map<String, Object>
*/
public static Map<String, Object> validToken(String token) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
JWSObject jwsObject = JWSObject.parse(token);
Payload payload = jwsObject.getPayload();
JWSVerifier verifier = new MACVerifier(SECRET); if (jwsObject.verify(verifier)) {
JSONObject jsonOBj = payload.toJSONObject();
// token校验成功(此时没有校验是否过期)
resultMap.put("state", TokenState.VALID.toString());
// 若payload包含ext字段,则校验是否过期
if (jsonOBj.containsKey("ext")) {
long extTime = Long.valueOf(jsonOBj.get("ext").toString());
long curTime = new Date().getTime();
// 过期了
if (curTime > extTime) {
resultMap.clear();
resultMap.put("state", TokenState.EXPIRED.toString());
}
}
resultMap.put("data", jsonOBj); } else {
// 校验失败
resultMap.put("state", TokenState.INVALID.toString());
} } catch (Exception e) {
//e.printStackTrace();
// token格式不合法导致的异常
resultMap.clear();
resultMap.put("state", TokenState.INVALID.toString());
}
return resultMap;
} }

TokenState

package com.jwt;

/**
* 枚举,定义token的三种状态
* @author running@vip.163.com
*
*/
public enum TokenState {
/**
* 过期
*/
EXPIRED("EXPIRED"),
/**
* 无效(token不合法)
*/
INVALID("INVALID"),
/**
* 有效的
*/
VALID("VALID"); private String state; private TokenState(String state) {
this.state = state;
} /**
* 根据状态字符串获取token状态枚举对象
* @param tokenState
* @return
*/
public static TokenState getTokenState(String tokenState){
TokenState[] states=TokenState.values();
TokenState ts=null;
for (TokenState state : states) {
if(state.toString().equals(tokenState)){
ts=state;
break;
}
}
return ts;
}
public String toString() {
return this.state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
} }

junit 测试结果

package com.jwt;

import java.util.Date;
import java.util.HashMap;
import java.util.Map; import org.junit.Test;
/**
* 单元测试(请自行引入junit4 Jar包)
*/
public class JwtTestCase {
@Test
@SuppressWarnings("unchecked")
public void test1() {
// 正常生成token----------------------------------------------------------------------------------------------------
String token = null;
Map<String, Object> payload = new HashMap<String, Object>();
Date date = new Date();
payload.put("uid", "");// 用户id
payload.put("iat", date.getTime());// 生成时间:当前
payload.put("ext", date.getTime() + * * );// 过期时间2小时
token = Jwt.createToken(payload);
System.out.println("新生成的token是:" + token+"\n马上将该token进行校验");
Map<String, Object> resultMap = Jwt.validToken(token);
System.out.println("校验结果是:" + getResult((String)resultMap.get("state")) );
HashMap<String,String> dataobj = (HashMap<String,String>) resultMap.get("data");
System.out.println("从token中取出的payload数据是:" +dataobj.toString()); } public void test2() {
// 校验过期----------------------------------------------------------------------------------------------------
String token = null;
Map<String, Object> payload = new HashMap<String, Object>();
Date date = new Date();
payload.put("uid", "");// 用户id
payload.put("iat", date.getTime());// 生成时间
payload.put("ext", date.getTime());// 过期时间就是当前
token = Jwt.createToken(payload);
System.out.println("新生成的token是:" + token+"\n马上将该token进行校验");
Map<String, Object> resultMap = Jwt.validToken(token);
System.out.println("校验结果是:" + getResult((String)resultMap.get("state")) ); } @SuppressWarnings("unchecked")
public void test2_1() {
// 不校验过期(当payload中无过期ext字段时)----------------------------------------------------------------------------------------------------
String token = null;
Map<String, Object> payload = new HashMap<String, Object>();
Date date = new Date();
payload.put("uid", "");// 用户id
payload.put("iat", date.getTime());// 生成时间
token = Jwt.createToken(payload);
System.out.println("新生成的token是:" + token+"\n马上将该token进行校验");
Map<String, Object> resultMap = Jwt.validToken(token);
System.out.println("校验结果是:" + getResult((String)resultMap.get("state")) );
HashMap<String,String> dataobj = (HashMap<String,String>) resultMap.get("data");
System.out.println("从token中取出的payload数据是:" +dataobj.toString()); } public void test3() {
// 校验非法token的情况----------------------------------------------------------------------------------------------------
String token = null;
Map<String, Object> payload = new HashMap<String, Object>();
Date date = new Date();
payload.put("uid", "");// 用户id
payload.put("iat", date.getTime());// 生成时间
payload.put("ext", date.getTime());// 过期时间就是当前 token = Jwt.createToken(payload);
System.out.println("新生成的token是:" + token);
System.out.println("将新生成的token加点调料再来进行校验");
token = token + "YouAreSB";
Map<String, Object> resultMap = Jwt.validToken(token);
System.out.println("校验结果是:" + getResult((String)resultMap.get("state")) );
System.out.println("原因是(非法token,payload参数可能经过中间人篡改,或者别人伪造的token)" ); } public void test4() {
// 校验异常的情况----------------------------------------------------------------------------------------------------
String token = "";
System.out.println("我胡乱传一个token:" + token);
Map<String, Object> resultMap = Jwt.validToken(token);
System.out.println("校验结果是:" + getResult((String)resultMap.get("state")) );
System.out.println("原因是(token格式不合法导致的程序异常)"); } public String getResult(String state) {
switch (TokenState.getTokenState(state)) {
case VALID:
//To do somethings
state = "有效token";
break;
case EXPIRED:
state = "过期token";
break;
case INVALID:
state = "无效的token";
break;
}
return state;
} }

loginServlet 使用,具体使用springmvc还是struts 可以参考servlet写法

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import net.minidev.json.JSONObject; import com.jwt.Jwt;
@WebServlet(urlPatterns="/servlet/login",loadOnStartup=)
public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 5285600116871825644L; /**
* 校验用户名密码
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { String userName=request.getParameter("userName");
String password =request.getParameter("password");
JSONObject resultJSON=new JSONObject(); //用户名密码校验成功后,生成token返回客户端
if("admin".equals(userName)&&"".equals(password)){
//生成token
Map<String , Object> payload=new HashMap<String, Object>();
Date date=new Date();
payload.put("uid", "admin");//用户ID
payload.put("iat", date.getTime());//生成时间
payload.put("ext",date.getTime()+**);//过期时间1小时
String token=Jwt.createToken(payload); resultJSON.put("success", true);
resultJSON.put("msg", "登陆成功");
resultJSON.put("token", token); }else{
resultJSON.put("success", false);
resultJSON.put("msg", "用户名密码不对");
}
//输出结果
output(resultJSON.toJSONString(), response); } public void output(String jsonStr,HttpServletResponse response) throws IOException{
response.setContentType("text/html;charset=UTF-8;");
PrintWriter out = response.getWriter();
out.println(jsonStr);
out.flush();
out.close(); } }

每次请求都需要验证token 是否有效

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import net.minidev.json.JSONObject; import com.jwt.Jwt;
@WebServlet(urlPatterns="/author/token",loadOnStartup=1,description="生成token的方法")
public class AuthorServlet extends HttpServlet { private static final long serialVersionUID = -8463692428988705309L; public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String token=request.getHeader("token");
System.out.println(token);
Map<String, Object> result=Jwt.validToken(token);
//转JSON并输出
PrintWriter out = response.getWriter();
out.println(new JSONObject(result).toJSONString());
out.flush();
out.close();
} public void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Map<String , Object> payload=new HashMap<String, Object>();
Date date=new Date();
payload.put("uid", "291969452");//用户id
payload.put("iat", date.getTime());//生成时间
payload.put("ext",date.getTime()+1000*60*60);//过期时间1小时
String token=null;
token=Jwt.createToken(payload); response.setContentType("text/html;charset=UTF-8;");
Cookie cookie=new Cookie("token", token);
cookie.setMaxAge(3600);
response.addCookie(cookie);
PrintWriter out = response.getWriter();
out.println(token);
out.flush();
out.close();
} }

调用获取信息的接口

mainServlet

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import net.minidev.json.JSONObject;
@WebServlet(urlPatterns="/servlet/getInfo",loadOnStartup=1)
public class mainServlet extends HttpServlet { private static final long serialVersionUID = -1643121334640537359L; @SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("正在调用获取信息的接口");
//将过滤器中存入的payload数据取出来
HashMap<String, String> data=(HashMap<String, String>) request.getAttribute("data");
//payload中的数据可以用来做查询,比如我们在登陆成功时将用户ID存到了payload中,我们可以将它取出来,去数据库查询这个用户的所有信息;
//而不是用request.getParameter("uid")方法来获取前端传给我们的uid,因为前端的参数时可篡改的不完全可信的,而我们从payload中取出来的数据是从token中
//解密取出来的,在秘钥没有被破解的情况下,它是绝对可信的;这样可以避免别人用这个接口查询非自己用户ID的相关信息
JSONObject resp=new JSONObject();
resp.put("success", true);
resp.put("msg", "成功");
resp.put("data", data);
output(resp.toJSONString(), response);
} public void output(String jsonStr,HttpServletResponse response) throws IOException{
response.setContentType("text/html;charset=UTF-8;");
PrintWriter out = response.getWriter();
out.println(jsonStr);
out.flush();
out.close(); } }

  

跨域过滤器

package com.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import com.thetransactioncompany.cors.CORSConfiguration;
import com.thetransactioncompany.cors.CORSFilter;
/**
* 服务端跨域处理过滤器,该过滤器需要依赖cors-filter-2.2.1.jar和java-property-utils-1.9.1.jar
* @author running@vip.163.com
*
*/
@WebFilter(urlPatterns={"/*"},asyncSupported=true,
initParams={
@WebInitParam(name="cors.allowOrigin",value="*"),
@WebInitParam(name="cors.supportedMethods",value="CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, TRACE"),
@WebInitParam(name="cors.supportedHeaders",value="token,Accept, Origin, X-Requested-With, Content-Type, Last-Modified"),//注意,如果token字段放在请求头传到后端,这里需要配置
@WebInitParam(name="cors.exposedHeaders",value="Set-Cookie"),
@WebInitParam(name="cors.supportsCredentials",value="true")
})
public class Filter0_CrossOriginResource extends CORSFilter implements javax.servlet.Filter{ public void init(FilterConfig config) throws ServletException {
System.out.println("跨域资源处理过滤器初始化了");
super.init(config);
} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("跨域过滤器");
super.doFilter(request, response, chain);
} public void setConfiguration(CORSConfiguration config) {
super.setConfiguration(config);
} }

验证登陆过滤器

package com.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map; import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import net.minidev.json.JSONObject; import com.jwt.Jwt;
import com.jwt.TokenState;
/**
* toekn校验过滤器,所有的API接口请求都要经过该过滤器(除了登陆接口)
* @author running@vip.163.com
*
*/
@WebFilter(urlPatterns="/servlet/*")
public class Filter1_CheckToken implements Filter { @Override
public void doFilter(ServletRequest argo, ServletResponse arg1,
FilterChain chain ) throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest) argo;
HttpServletResponse response=(HttpServletResponse) arg1;
// response.setHeader("Access-Control-Allow-Origin", "*");
if(request.getRequestURI().endsWith("/servlet/login")){
//登陆接口不校验token,直接放行
chain.doFilter(request, response);
return;
}
//其他API接口一律校验token
System.out.println("开始校验token");
//从请求头中获取token
String token=request.getHeader("token");
Map<String, Object> resultMap=Jwt.validToken(token);
TokenState state=TokenState.getTokenState((String)resultMap.get("state"));
switch (state) {
case VALID:
//取出payload中数据,放入到request作用域中
request.setAttribute("data", resultMap.get("data"));
//放行
chain.doFilter(request, response);
break;
case EXPIRED:
case INVALID:
System.out.println("无效token");
//token过期或者无效,则输出错误信息返回给ajax
JSONObject outputMSg=new JSONObject();
outputMSg.put("success", false);
outputMSg.put("msg", "您的token不合法或者过期了,请重新登陆");
output(outputMSg.toJSONString(), response);
break;
} } public void output(String jsonStr,HttpServletResponse response) throws IOException{
response.setContentType("text/html;charset=UTF-8;");
PrintWriter out = response.getWriter();
// out.println();
out.write(jsonStr);
out.flush();
out.close(); } @Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("token过滤器初始化了");
} @Override
public void destroy() { } }

  

jsp页面测试代码:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%> <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
</head>
<body>
<button id="gettoken">点击ajax获取token</button>
<textarea id="token" rows="5" cols="25" style="width: 300px;" placeholder="token值"></textarea>
<br />
<br />
<button id="validtoken">点击解析上面的token</button><br/> <textarea id="result" readonly rows="5" cols="25" style="width: 300px;" placeholder="数据解析结果"></textarea> <script src="jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script>
<script>
$(function () {
$("#gettoken").on("click",function () {
$.ajax({
type:"put",
url:"http://localhost:8080/JWT/author/token",
async:true,
success:function(data){
$("#token").val(data);
}
});
}); $("#validtoken").on('click',function (e) {
var token=$.trim($("#token").val());
if(!token.length){
alert("请先获取token");
return;
}
$.ajax({
type:"get",
dataType:"json",
url:"http://localhost:8080/JWT/author/token?r="+Math.random(),
async:true,
beforeSend: function(request) {
request.setRequestHeader("token", token);
},
success:function (data) {
$("#result").val(JSON.stringify(data));
}
});
}); })
</script>
</body>
</html>

  

具体代码地址:https://github.com/bigmeow/JWT

webapp用户身份认证方案 JSON WEB TOKEN 实现的更多相关文章

  1. 身份认证:JSON Web Token

    JSON Web Token(JWT)是一种基于JSON的开放标准((RFC 7519),也是目前最流行的跨域认证解决方案. 传统的 cookie 认证方式看起来遵守了 REST 架构的无状态要求,但 ...

  2. 把旧系统迁移到.Net Core 2.0 日记 (18) --JWT 认证(Json Web Token)

    我们最常用的认证系统是Cookie认证,通常用一般需要人工登录的系统,用户访问授权范围的url时,会自动Redirect到Account/Login,登录后把认证结果存在cookie里. 系统只要找到 ...

  3. 基于 Token 的身份验证:JSON Web Token(附:Node.js 项目)

    最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强, ...

  4. 基于 Token 的身份验证:JSON Web Token

    最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强, ...

  5. 基于 Token 的身份验证:JSON Web Token(JWT)

    1.传统身份验证和JWT的身份验证 传统身份验证:       HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用.这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过 ...

  6. 理解JWT(JSON Web Token)认证及python实践

    原文:https://segmentfault.com/a/1190000010312468?utm_source=tag-newest 几种常用的认证机制 HTTP Basic Auth HTTP ...

  7. 理解JSON Web Token (一)

    一:理解单系统登录的原理及实现? web应用采用的 browser/server 架构的,http是无状态协议的,也就是说用户从A页面跳转到B页面会发起http请求,当服务器返回响应后,当用户A继续访 ...

  8. ASP.NET Web API 2系列(四):基于JWT的token身份认证方案

    1.引言 通过前边的系列教程,我们可以掌握WebAPI的初步运用,但是此时的API接口任何人都可以访问,这显然不是我们想要的,这时就需要控制对它的访问,也就是WebAPI的权限验证.验证方式非常多,本 ...

  9. Laravel 5 中使用 JWT(Json Web Token) 实现基于API的用户认证

    在JavaScript前端技术大行其道的今天,我们通常只需在后台构建API提供给前端调用,并且后端仅仅设计为给前端移动App调用.用户认证是Web应用的重要组成部分,基于API的用户认证有两个最佳解决 ...

随机推荐

  1. Webapi 跨域 解决解决错误No 'Access-Control-Allow-Origin' header is present on the requested resource 问题

    首先是web端(http://localhost:53784) 请求 api(http://localhost:81/api/)时出现错误信息: 查看控制台会发现错误:XMLHttpRequest c ...

  2. workflow的简介

    工作流(Workflow) 是对工作流程及其各操作步骤之间业务规则的抽象.概括描述.工作流建模,即将工作流程中的工作如何前后组织在一起的逻辑和规则,在计算机中以恰当的模型表达并对其实施计算. 工作流要 ...

  3. linux云主机cpu一直很高降不下来,系统日志报nf_conntrack: table full, dropping packet.

    在启用了iptables web服务器上,流量高的时候经常会出现下面的错误: ip_conntrack: table full, dropping packet 这个问题的原因是由于web服务器收到了 ...

  4. swift 学习- 21 -- 类型转换

    // 类型转换 可以判断实例的类型, 也可以将实例看做其父类的或者子类的实例 // 类型转换在 Swift 中使用 is 和 as 操作符实现, 这两个操作符提供了一种简单达意的方式去检查值的类型 或 ...

  5. Confluence 6 SQL Server 测试你的数据库连接

    在你的数据库设置界面,有一个 测试连接(Test connection)按钮可以检查: Confluence 可以连接你的数据库服务器 数据库字符集和隔离级别是正确的 你的数据库用户有正确的数据库权限 ...

  6. Confluence 6 注册外部小工具

    你可以从外部站点中注册小工具(Gadget)(例如 Jira 应用),你注册成功的小工具将会在 宏浏览器中显示出来,使用你 Confluence 站点的用户可以使用 Gadget Macro 来调用它 ...

  7. mac 端口占用问题

    查看端口号 终端输入:sudo lsof -i tcp:port 将port换成被占用的端口(如:8086.9998) 将会出现占用端口的进程信息. 杀死占用端口的PID进程 找到进程的PID,使用k ...

  8. ob_start用法详解

    用PHP的ob_start(); 一. 相关函数简介:1.Flush:刷新缓冲区的内容,输出.函数格式:flush()说明:这个函数经常使用,效率很高.2.ob_start :打开输出缓冲区函数格式: ...

  9. 反向找related_name以及limit_fields_to

    问题2:客户的添加页面,通过popup创建用户时 解决方案: 如果新创建的用户时:如果是销售部的人,页面才增加 目的是:拿到limit_choices_to,就可以判断了 当有两个Foreignkey ...

  10. yum安装软件内容

    linux  yum源改为阿里yum源 1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.back ...