Sprint Boot使用OAuth和JWT實現身份認證【一】
阿新 • • 發佈:2019-01-05
依賴第三方庫:
compile 'io.jsonwebtoken:jjwt:0.9.0'
1.先寫個例子用JWT實現獲取token和解析token
package com.yf.gyy.OAuthController; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @RestController @Api(value="OAuth2測試") public class OAuthController { @ApiOperation(value="測試獲取token",httpMethod="POST") @RequestMapping("/api/testOAuth/GetToken") public String CreateToken() { Map<String, Object> claims=new HashMap<String, Object>(); claims.put("zzzili", "zz"); String token = Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + 6000 * 1000)) .signWith(SignatureAlgorithm.HS512,"thisiskey") //採用什麼演算法是可以自己選擇的,不一定非要採用HS512 .compact(); return token; } @ApiOperation(value="測試token解析",httpMethod="POST") @RequestMapping("/api/testOAuth/TestOAuth2") public Claims TestOAuth2( @RequestBody String token) { System.out.println(token); Claims claims = Jwts.parser() .setSigningKey("thisiskey") .parseClaimsJws(token) .getBody(); return claims; } }
2.對spring boot專案新增OAuth過濾器
我們暫且實現,自己定義獲取token的方法和自定義過濾器
需要4個java類
>>http請求過濾器類,繼承自 OncePerRequestFilter
>>過濾器註冊類,繼承自 WebSecurityConfigurerAdapter
>>使用者物件類,繼承自 UserDetails
>>使用者物件加密解密類
2.1新增一個rest方法,用以獲取token:
@ApiOperation(value="測試獲取token",httpMethod="POST") @RequestMapping("/api/testOAuth2/GetToken") public String CreateToken() { List<String> roles = new ArrayList<String>(); roles.add("USER"); roles.add("ADMIN"); Map<String, Object> u=new HashMap<String, Object>(); u.put("password", "zzz"); u.put("username", "zzz"); u.put("created", "zzz"); String token = Jwts.builder() .setClaims(u) .setExpiration(new Date(System.currentTimeMillis() + 6000 * 1000)) .signWith(SignatureAlgorithm.HS512,"123456") //採用什麼演算法是可以自己選擇的,不一定非要採用HS512 .compact(); return token; }
2.2自定義http請求過濾器
package com.yf.gyy; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String tokenHeader = "Authorization"; String tokenHead = "Bearer "; String authHeader = request.getHeader(tokenHeader); if (authHeader != null && authHeader.startsWith(tokenHead)) { final String authToken = authHeader.substring(tokenHead.length()); Claims claims = Jwts.parser() .setSigningKey("123456") .parseClaimsJws(authToken) .getBody(); String username = claims.get("username").toString(); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( request)); //logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } private UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<String> roles = new ArrayList<String>(); roles.add("USER"); roles.add("ADMIN"); Calendar c = Calendar.getInstance(); c.setTime(new Date()); c.add(Calendar.DAY_OF_MONTH, 10);// 今天+10天 Date nowT= c.getTime(); JwtUser user = new JwtUser("zzz", "zzz", "zzz", "email", roles, nowT); return user; } }
package com.yf.gyy;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* @author
* JwtTokenUtil
*/
//@Component("jwtTokenUtil")
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
static final String CLAIM_KEY_USERNAME = "username";
static final String CLAIM_KEY_PASSWORD = "password";
static final String CLAIM_KEY_CREATED = "created";
//private static final String AUDIENCE_UNKNOWN = "unknown";
//private static final String AUDIENCE_WEB = "web";
//private static final String AUDIENCE_MOBILE = "mobile";
//private static final String AUDIENCE_TABLET = "tablet";
//通過@value註解獲取金鑰
//@Value("123456")
private String secret="123456";
//通過@value註解獲取失效時間
//@Value("${jwt.expiration}")
private Long expiration=300L;
//@Resource(name = "tokenConfig")
//private Map<String, String> tokenConfig;
/**
* @param token
* return username by Token
*/
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = (String) claims.get(CLAIM_KEY_USERNAME);
//username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* @param token
* return CreatedDate by Token 獲取token建立時間
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
/**
* @param token
* return ExpirationDate by Token 獲取token過期時間
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
/**
* @param token
* return Password by Token 獲取token的密碼
*/
public String getPasswordFromToken(String token) {
String password;
try {
final Claims claims = getClaimsFromToken(token);
password = (String) claims.get(CLAIM_KEY_PASSWORD);
} catch (Exception e) {
password = null;
}
return password;
}
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
//設定簽名的金鑰
.setSigningKey(secret)
//把token轉換成斷言
.parseClaimsJws(token)
.getBody();
//Date expirationTime = Jwts.parser().setSigningKey(secret).parseClaimsJwt(token).getBody().getExpiration();
} catch (Exception e) {
claims = null;
}
return claims;
}
//生成到期日期
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
//判斷token是否過期
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//判斷token建立時間是否在密碼重置之前
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;
}
//忽略token的過期時間
private Boolean ignoreTokenExpiration(String token) {
String audience = getPasswordFromToken(token);
return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
}*/
//生成token
public String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
//判斷token是否可以重新整理
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getCreatedDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token)) ;
/*|| ignoreTokenExpiration(token))*/
}
//重新整理token
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
//判讀token是否有效
public Boolean validateToken(String token, UserDetails userDomain) {
// JwtUser user = (JwtUser) userPO;
final String username = getUsernameFromToken(token);
//final Date created = getCreatedDateFromToken(token);
//final Date expiration = getExpirationDateFromToken(token);
if(username==null){
return false;
}else{
return (
username.equals(userDomain.getUsername())
&& !isTokenExpired(token));
//&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()));
}
}
//public String getTokenKey() {
// return tokenConfig.get("tokenKey");
//}
}
package com.yf.gyy;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class JwtUser implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private final String id;
private final String username;
private final String password;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;
private final Date lastPasswordResetDate;
public JwtUser(
String id,
String username,
String password,
String email,
List<String> authorities,
Date lastPasswordResetDate) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.authorities = mapToGrantedAuthorities(authorities);
this.lastPasswordResetDate = lastPasswordResetDate;
}
private List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
//返回分配給使用者的角色列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@JsonIgnore
public String getId() {
return id;
}
@JsonIgnore
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 賬戶是否未過期
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
// 賬戶是否未鎖定
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
// 密碼是否未過期
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 賬戶是否啟用
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
// 這個是自定義的,返回上次密碼重置日期
@JsonIgnore
public Date getLastPasswordResetDate() {
return lastPasswordResetDate;
}
public String getEmail() {
return email;
}
}
2.3將過濾器裝在到webconfig中
package com.yf.gyy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.requestMatchers().anyRequest();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
}
3.在rest介面中使用標註新增訪問許可權:
package com.yf.gyy.OAuthController;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@RestController
@Api(value="OAuth2測試")
//@PreAuthorize("hasRole('ROLE_USER')") //判斷角色
@PreAuthorize("principal.username!=null") //使用者名稱不為空
public class OAuthController {
@ApiOperation(value="測試訪問授權",httpMethod="POST")
@RequestMapping("/api/testOAuth/TestAccessToken")
public String TestAccessToken()
{
return "test oauth";
}
}