JWT实作(五)(Day9)

讲到权限之前,我们必须谈谈spring security的Filter Chain(过滤器链)

Key filters in the chain are (in the order)

  • SecurityContextPersistenceFilter (restores Authentication from JSESSIONID)
  • UsernamePasswordAuthenticationFilter (performs authentication)
  • ExceptionTranslationFilter (catch security exceptions from FilterSecurityInterceptor)
  • FilterSecurityInterceptor (may throw authentication and authorization exceptions)

SecurityContextPersistenceFilter: 会将登入後的SecurityContext存入HttpSession里面,
之後的filter会依赖这个SecurityContext获取登入的状态,
但是我们因为是API,所以希望每次的请求都是无状态的,因此我们会禁用HttpSession产生。

UsernamePasswordAuthenticationFilter: 会去验证 request 里 username&password 的属性名,
然後去判断是不是走/login路径进来的,这其实是给form表单验证使用的,对我们现在这个API无效。
我们会自己实作过滤器,当登入成功之後把Authentication物件存到SecurityContext里面。

FilterSecurityInterceptor: 抛出与验证有关的错误。只要要前往的requestUrl是受保护的话,都会判断是否已经验证过,来决定能不能pass。

  • Authentication: 确认这个人是谁 ( Who the person )
  • Authorization: 可以允许这个人做什麽 ( Allow the person to do what)

ExceptionTranslationFilter: 捕捉FilterSecurityInterceptor 抛出的错误。

好的,让我们开始实作权限认证的功能吧

修改一下我们User的建构子

package com.stockAPI.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {
	
	private Integer id;
	
	private String account;
	
	private String name;
	
	private String password;
	
	private String authority;
	
	public User() {
		
	}
	
	
	public User(String account,String name,String password,String authority) {
		this.account=account;
		this.name=name;
		this.password=password;
		this.authority=authority;
	}

}

首先,让我们实作JWTCheckFilter 这个验证过滤器

package com.stockAPI.filter;

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

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.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.stockAPI.model.StockUser;
import com.stockAPI.model.User;
import com.stockAPI.service.JWTService;

@Component
public class JWTCheckFilter extends OncePerRequestFilter {
	
	@Autowired
    private JWTService jwtService;


	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//取得标头的authorization属性
		String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
	    if (authHeader != null) {
	        String accessToken = authHeader.replace("Bearer ", "");

	        Map<String, Object> claims = jwtService.parseToken(accessToken);
	   
	        Integer user_id = (Integer) claims.get("user_id");
	        String account = (String) claims.get("account");
	        String name = (String) claims.get("name");
	        String authority = (String) claims.get("authority");
	        
	        User user = new User(account,name,null,authority);
	        user.setId(user_id);
	        StockUser stockUser =new StockUser(user);

	        Authentication authentication =
	                new UsernamePasswordAuthenticationToken(user, null, stockUser.getAuthorities());
	        SecurityContextHolder.getContext().setAuthentication(authentication);
	    }

	    filterChain.doFilter(request, response);
		
	}

}

然後修改一下SecurityConfig设定

package com.stockAPI.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.stockAPI.filter.JWTCheckFilter;
import com.stockAPI.service.StockUserService;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	StockUserService stockUserService;
	
	@Autowired
	JWTCheckFilter jWTCheckFilter;
	
		@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
			auth.userDetailsService(stockUserService).
			passwordEncoder(new BCryptPasswordEncoder());
	    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http
    		.authorizeRequests()
    		.antMatchers("/user/create").hasAuthority("ADMIN") //管理员可以新增使用者资料
    		.antMatchers("/user/testUnblock").permitAll()
    		.antMatchers("/user/login").permitAll()
    		.antMatchers("/user/search/**").permitAll() //大家都可以查询资料
    		.and()
				//新增过滤器设定
    		.addFilterBefore(jWTCheckFilter, UsernamePasswordAuthenticationFilter.class)
        .sessionManagement()
				//关闭HttpSession的建立状态
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    		.and()
    		.csrf().disable();
        
    }
    
    //加密器注册容器
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    //验证类别注册容器
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

再来我们要写postman的测试,首先我们要先储存我们登入所获得的JWT,然後把JWT放入postman的环境变数,
标头名称Authorization 会放置 token 变数,然後我们再去请求其他带有权限验证的的连结,
来测试我们新加的过滤器是否有效。

postman设置环境变数
https://ithelp.ithome.com.tw/upload/images/20210923/20138857NNh0VXU3Jy.png

撰写测试
https://ithelp.ithome.com.tw/upload/images/20210923/20138857x104UntjZM.png

接着点选你的collection,把Type改成Bear Token
将Token栏位改成 { { token } } ,postman会自动帮我们把环境变数塞进去
https://ithelp.ithome.com.tw/upload/images/20210923/20138857NdPgvqpn03.png

接着按送出,查看环境变数是否成功储存token
https://ithelp.ithome.com.tw/upload/images/20210923/20138857Bernz2lpRW.png

再来检查我们ken123的使用者权限
https://ithelp.ithome.com.tw/upload/images/20210923/201388577E8xuAyYbs.png

可以发现我们目前的权限是Normal,所以当我们去请求创建使用者连结时
https://ithelp.ithome.com.tw/upload/images/20210923/20138857OGu5nQH0Xr.png

此时我们修改SQL资料库中 ken123的权限栏位 NORMAL→ADMIN

UPDATE `stockapi`.`users` SET `AUTHORITY` = 'ADMIN' WHERE `ACCOUNT` = 'ken123' ;

重新登入取得token後再重新请求一次创造使用者的连结
https://ithelp.ithome.com.tw/upload/images/20210923/201388573LZwvm2AXX.png

就会发现请求成功罗!


参考资料:

https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works

https://medium.com/@yovan/spring-security-architecture-6dbac2a16bda


<<:  D23-(9/23)-宅配通(2642)-航运、空运、不能错过陆运

>>:  D16 - 转移资料到TiDB工具介绍(三)

【把玩Azure DevOps】Day30 2021铁人赛结尾感言

终於来到了铁人赛的最後一天,按照惯例在最後一天的文章是用来结尾的,所以并没有任何的技术含量,只是聊聊...

渗透测试基础篇

进入渗透测试篇~ 今天有点忙碌,没甚麽时间废话XD 照惯例,每篇文章都会附上第一篇的文章,让大家了解...

月费如何定价?免费试用会提高订阅率吗?

去年因为肺炎导致广告收入骤降,加上iOS的IDFA政策的双重打击,我们决定开始做月费制的功能。 上线...

[Day8]Rare Easy Problem

上一篇介绍了2 the 9s,是一题会需要重复执行的题目,使用回圈跟副程序会比较容易执行,整体上不会...

Day29:HTML(27) form(6)

输入类型密码 "input type="password""...