1. 程式人生 > >Sprint Boot使用OAuth和JWT實現身份認證【一】

Sprint Boot使用OAuth和JWT實現身份認證【一】

依賴第三方庫:

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";
	}
}