讲到权限之前,我们必须谈谈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。
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设置环境变数
撰写测试
接着点选你的collection,把Type改成Bear Token
将Token栏位改成 { { token } } ,postman会自动帮我们把环境变数塞进去
接着按送出,查看环境变数是否成功储存token
再来检查我们ken123的使用者权限
可以发现我们目前的权限是Normal,所以当我们去请求创建使用者连结时
此时我们修改SQL资料库中 ken123的权限栏位 NORMAL→ADMIN
UPDATE `stockapi`.`users` SET `AUTHORITY` = 'ADMIN' WHERE `ACCOUNT` = 'ken123' ;
重新登入取得token後再重新请求一次创造使用者的连结
就会发现请求成功罗!
参考资料:
https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works
https://medium.com/@yovan/spring-security-architecture-6dbac2a16bda
<<: D23-(9/23)-宅配通(2642)-航运、空运、不能错过陆运
终於来到了铁人赛的最後一天,按照惯例在最後一天的文章是用来结尾的,所以并没有任何的技术含量,只是聊聊...
进入渗透测试篇~ 今天有点忙碌,没甚麽时间废话XD 照惯例,每篇文章都会附上第一篇的文章,让大家了解...
去年因为肺炎导致广告收入骤降,加上iOS的IDFA政策的双重打击,我们决定开始做月费制的功能。 上线...
上一篇介绍了2 the 9s,是一题会需要重复执行的题目,使用回圈跟副程序会比较容易执行,整体上不会...
输入类型密码 "input type="password""...