開放平臺就是將企業中的業務的核心部分通過抽象和提取,造成面向企業或者面向用戶的增值系統,爲企業帶來新的業務增漲點。java
由於是企業的核心業務能力,因此平臺的安全性就成爲重中之重。mysql
普通的接口使用Token令牌的方案就能夠保證,可是對於一些敏感的接口就須要有針對性的處理,好比使用https。git
https是在http超文本傳輸協議加入SSL層,它在網絡間通訊是加密的,因此須要加密證書。github
https協議須要ca證書,通常須要交費。web
簽名的設計通常是經過用戶和密碼的校驗,而後針對用戶生成一個惟一的Token令牌,redis
用戶再次獲取信息時,帶上此令牌,若是令牌正確,則返回數據。對於獲取Token信息後,訪問用戶相關接口,客戶端請求的url須要帶上以下參數:算法
時間戳:timestampspring
Token令牌:tokensql
JWT(json web token)是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準。json
JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源。好比用在用戶登陸上。
那麼jwt到底長什麼樣呢?
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload),第三部分是簽證(signature)。
header
jwt的頭部承載兩部分信息:
完整的頭部就像下面這樣的JSON:
{
"typ": "JWT",
"alg": "HS256"
}
而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
playload
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分
標準中註冊的聲明 (建議但不強制使用) :
公共的聲明 :
公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密.
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
定義一個payload:
{
"name":"Free碼農",
"age":"28",
"org":"今日頭條"
}
而後將其進行base64加密,獲得Jwt的第二部分:
eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9
signature
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
header (base64後的)
payload (base64後的)
secret
這個部分須要base64加密後的header和base64加密後的payload使用.鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret組合加密,而後就構成了jwt的第三部分:
49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY
密鑰secret是保存在服務端的,服務端會根據這個密鑰進行生成token和驗證,因此須要保護好。
下面是一個JWT的工做流程圖。
/protected
中的API時,在請求的header中加入 Authorization: Bearer xxxx(token)
。此處注意token以前有一個7字符長度的 Bearer
首先,加入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
配置信息代碼以下:
# JACKSON
spring:
jackson:
serialization:
INDENT_OUTPUT: true
jwt:
header: Authorization
secret: mySecret
expiration: 604800
route:
authentication:
path: auth
refresh: refresh
token處理類爲JwtTokenUtil,代碼以下:
@Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -3301605591108950415L; static final String CLAIM_KEY_USERNAME = "sub"; static final String CLAIM_KEY_AUDIENCE = "aud"; static final String CLAIM_KEY_CREATED = "iat"; static final String AUDIENCE_UNKNOWN = "unknown"; static final String AUDIENCE_WEB = "web"; static final String AUDIENCE_MOBILE = "mobile"; static final String AUDIENCE_TABLET = "tablet"; @Autowired private TimeProvider timeProvider; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getIssuedAtDateFromToken(String token) { return getClaimFromToken(token, Claims::getIssuedAt); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public String getAudienceFromToken(String token) { return getClaimFromToken(token, Claims::getAudience); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(timeProvider.now()); } private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { return (lastPasswordReset != null && created.before(lastPasswordReset)); } private String generateAudience(Device device) { String audience = AUDIENCE_UNKNOWN; if (device.isNormal()) { audience = AUDIENCE_WEB; } else if (device.isTablet()) { audience = AUDIENCE_TABLET; } else if (device.isMobile()) { audience = AUDIENCE_MOBILE; } return audience; } private Boolean ignoreTokenExpiration(String token) { String audience = getAudienceFromToken(token); return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience)); } public String generateToken(UserDetails userDetails, Device device) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername(), generateAudience(device)); } private String doGenerateToken(Map<String, Object> claims, String subject, String audience) { final Date createdDate = timeProvider.now(); final Date expirationDate = calculateExpirationDate(createdDate); System.out.println("doGenerateToken " + createdDate); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setAudience(audience) .setIssuedAt(createdDate) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { final Date created = getIssuedAtDateFromToken(token); return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && (!isTokenExpired(token) || ignoreTokenExpiration(token)); } public String refreshToken(String token) { final Date createdDate = timeProvider.now(); final Date expirationDate = calculateExpirationDate(createdDate); final Claims claims = getAllClaimsFromToken(token); claims.setIssuedAt(createdDate); claims.setExpiration(expirationDate); return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { JwtUser user = (JwtUser) userDetails; final String username = getUsernameFromToken(token); final Date created = getIssuedAtDateFromToken(token); //final Date expiration = getExpirationDateFromToken(token); return ( username.equals(user.getUsername()) && !isTokenExpired(token) && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()) ); } private Date calculateExpirationDate(Date createdDate) { return new Date(createdDate.getTime() + expiration * 1000); } }
最後,在控制層對token的處理進行調用,就可以完成用戶的權限認證。
啓動應用,而後輸入http://localhost:8080,咱們可以看到測試頁面
當輸入用戶名爲admin而且登陸成功時,點擊右側的按鈕可以調用相應的接口。當登陸不成功時,會返回401錯誤。
當輸入用戶名爲user而且登陸成功時,只能訪問普通用戶權限的接口,不能訪問管理用戶權限的接口。
關於開放平臺其實還有不少須要切入的點,此處給出的安全方案只是一個示例,能夠在此基礎上進行二次開發,實現企業級的安全方案。文中的示例代碼地址以下: