iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Modern Web

誤打誤撞學了Spring Boot 還當了後端工程師系列 第 28

Day 28 - Spring Security (五) JwtAuthenticationProvider

實作

新增依賴

<!-- JWT -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.11.2</version>
</dependency>

<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.11.2</version>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.11.2</version>
	<scope>runtime</scope>
</dependency>

新增工具類

package com.example.iThomeIronMan.util;

import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

public class JwtUtil {

	private static final long EXPIRATIONTIME = 432_000_000;     // 5天
	private static final String TOKEN_PREFIX = "Bearer";        // Token前缀
	private static final String HEADER_STRING = "Authorization";// 存放Token的Header Key
	private static final String KEY = "VincentIsRunningBlogForProgrammingBeginnerSpringSecurityAndJWT";	//給定一組密鑰,用來解密以及加密使用
	private static final Key secretKey = Keys.hmacShaKeyFor(KEY.getBytes());
	
	public static void generateToken(HttpServletResponse response, String account) {
						
		Claims claims = Jwts.claims();
		claims.put("account", account);
		claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME));
		
		String token = Jwts.builder().setClaims(claims).signWith(secretKey).compact();
		response.setContentType("application/json");
		response.setHeader(HEADER_STRING, token);
	}
	
	public static Map<String, Object> parseToken(HttpServletRequest request) {

    // 從request的header拿回token
    String token = request.getHeader(HEADER_STRING);
    if(token != null) {
			JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
			Claims claims = parser.parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();

	        return claims.entrySet().stream()
				        			 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
		}
		return null;
	}
}

修改CustomAuthenticationSuccessHandler

package com.example.iThomeIronMan.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.example.iThomeIronMan.util.JwtUtil;

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		// TODO Auto-generated method stub
		
		JwtUtil.generateToken(response, authentication.getName());
	  response.sendRedirect("/information");
	}

}

新增JwtAuthenticationFilter

package com.example.iThomeIronMan.filter;

import java.io.IOException;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.web.filter.GenericFilterBean;

import com.example.iThomeIronMan.util.JwtUtil;

public class JwtAuthenticationFilter extends GenericFilterBean {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// TODO Auto-generated method stub

		HttpServletRequest req = (HttpServletRequest) request;

		String uri = new String(req.getRequestURI());
		
		// 放行所有靜態檔案
		if(uri.contains("/css") || uri.contains("/images") || uri.contains("/js")) {
			chain.doFilter(request, response);
			return ;
		}

		if(uri.contains("/login") || uri.contains("/register")) {
			chain.doFilter(request, response);
			return ;
		}

    String token = req.getHeader("Authorization");
		//驗證token,獲得授權
		Map<String, Object> claims = JwtUtil.parseToken(req);
		if(claims != null) {
			System.err.println(claims);
			chain.doFilter(request, response);
		}
	}

}

修改SecurityConfig

package com.example.iThomeIronMan.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.iThomeIronMan.filter.JwtAuthenticationFilter;
import com.example.iThomeIronMan.security.CustomAuthenticationFailureHandler;
import com.example.iThomeIronMan.security.CustomAuthenticationProvider;
import com.example.iThomeIronMan.security.CustomAuthenticationSuccessHandler;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserDetailsService userDetailsService;

	@Autowired
	private CustomAuthenticationProvider authProvider;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// TODO Auto-generated method stub
		auth.authenticationProvider(authProvider);
	}

	@Override
    protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
        http.authorizeRequests()
        	// 設定放行名單
			.antMatchers("/login", "/register").permitAll()
            // 其餘路徑皆須進行驗證
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login").loginProcessingUrl("/login").usernameParameter("account").passwordParameter("password")
            .successHandler(new CustomAuthenticationSuccessHandler())
            .failureHandler(new CustomAuthenticationFailureHandler())
            .and()
            .logout().logoutUrl("/logout")
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            // 關閉CSRF(跨站請求偽造)攻擊的防護,這樣才不會拒絕外部直接對API 發出的請求,例如Postman 與前端
            .csrf().disable();
    }

	@Override
	public void configure(WebSecurity web) throws Exception {
		// TODO Auto-generated method stub
		web.ignoring().antMatchers("/css/**", "/images/**", "/js/**");
	}

}

上一篇
Day 27 - Spring Security (四) JWT
下一篇
Day 29 - 開發流程(上) 瀑布式(Waterfall Model) & 敏捷式(Agile Model)
系列文
誤打誤撞學了Spring Boot 還當了後端工程師30

尚未有邦友留言

立即登入留言