说明
前后端分离的项目,需要后端认证成功之后,颁发令牌 token
,下次客户端在请求资源的时候,需要携带 access_token
参数。
Spring Security 生成 token 的基本思路是:
- 表单认证之后,在
successHandler
中根据 Authentication
生成 token,响应给前端
- 创建 Filter
TokenAuthenticationFilter
当前请求参数中有参数 access_token
,解析token,获取 Authentication
放入 SecurityContext 上下文中。
开发
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.rick.common</groupId>
<artifactId>sharp-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
token授权过滤器 TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String accessToken = request.getParameter("access_token");
if (StringUtils.isBlank(accessToken)) {
chain.doFilter(request, response);
return;
}
try {
Authentication authentication = JWTUtils.toAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} catch (Exception e) {
HttpServletResponseUtils.write(response, "application/json;charset=UTF-8"
, JsonUtils.toJson(ResultUtils.exception(403, e.getMessage())));
}
}
}
安全配置 WebSecurityConfig.java
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled=true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl) requests.anyRequest()).authenticated();
})
.formLogin()
// 1. 认证成功后将token响应给前端
.successHandler((request, response, authentication) -> {
String token = JWTUtils.createToken(authentication);
HttpServletResponseUtils.write(response, "application/json;charset=UTF-8"
, JsonUtils.toJson(ResultUtils.success(token)));
})
.and().exceptionHandling().authenticationEntryPoint((request, response, e) -> {
HttpServletResponseUtils.write(response, "application/json;charset=UTF-8"
, JsonUtils.toJson(ResultUtils.exception(403, e.getMessage())));
})
.and().exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
HttpServletResponseUtils.write(response, "application/json;charset=UTF-8"
, JsonUtils.toJson(ResultUtils.exception(403, e.getMessage())));
}
}).and().addFilterBefore(new TokenAuthenticationFilter(), BasicAuthenticationFilter.class)
.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
- 添加token授权过滤器
TokenAuthenticationFilter
- 表单认证之后,在
successHandler
中根据 Authentication
生成 token,响应给前端
- 认证授权的异常处理
添加用户信息 application.yml
spring:
security:
user:
name: rick
password: 123456
roles: ADMIN
API接口 IndexController.java
@RestController
public class IndexController {
@GetMapping
public String index() {
return "index";
}
@GetMapping("admin")
public String admin(Authentication authentication) {
return "admin ==> ";
}
@GetMapping("p1")
@PreAuthorize("hasRole('p1')")
public String p1(Authentication authentication) {
return authentication.getPrincipal().toString();
}
}
测试
没有携带token
curl -X GET \
http://127.0.0.1:8080/admin
响应
{
"success": false,
"code": 403,
"msg": "Full authentication is required to access this resource"
}
认证
curl -X POST \
'http://127.0.0.1:8080/login?username=rick&password=123456'
响应
{
"success": true,
"code": 0,
"msg": "OK",
"data": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC94aG9wZS50b3AiLCJzdWIiOiJvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUudXNlcmRldGFpbHMuVXNlciBbVXNlcm5hbWU9cmljaywgUGFzc3dvcmQ9W1BST1RFQ1RFRF0sIEVuYWJsZWQ9dHJ1ZSwgQWNjb3VudE5vbkV4cGlyZWQ9dHJ1ZSwgY3JlZGVudGlhbHNOb25FeHBpcmVkPXRydWUsIEFjY291bnROb25Mb2NrZWQ9dHJ1ZSwgR3JhbnRlZCBBdXRob3JpdGllcz1bUk9MRV9BRE1JTl1dIiwiZXhwIjoxNjM0MzYyMzIxLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl19.e4-tJzixAkOk9xeoX9zkehYjaH_DmBPe9tho6cCtu6M"
}
携带token
curl -X GET \
'http://127.0.0.1:8080/admin?access_token=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC94aG9wZS50b3AiLCJzdWIiOiJvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUudXNlcmRldGFpbHMuVXNlciBbVXNlcm5hbWU9cmljaywgUGFzc3dvcmQ9W1BST1RFQ1RFRF0sIEVuYWJsZWQ9dHJ1ZSwgQWNjb3VudE5vbkV4cGlyZWQ9dHJ1ZSwgY3JlZGVudGlhbHNOb25FeHBpcmVkPXRydWUsIEFjY291bnROb25Mb2NrZWQ9dHJ1ZSwgR3JhbnRlZCBBdXRob3JpdGllcz1bUk9MRV9BRE1JTl1dIiwiZXhwIjoxNjM0MzYyMzIxLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl19.e4-tJzixAkOk9xeoX9zkehYjaH_DmBPe9tho6cCtu6M'
响应:token正确
admin ==>
能正常访问资源信息。
响应:如果token错误
{
"success": false,
"code": 403,
"msg": "无效的token"
}
访问没有权限的资源 /p
curl -X GET \
'http://127.0.0.1:8080/p1?access_token=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC94aG9wZS50b3AiLCJzdWIiOiJvcmcuc3ByaW5nZnJhbWV3b3JrLnNlY3VyaXR5LmNvcmUudXNlcmRldGFpbHMuVXNlciBbVXNlcm5hbWU9cmljaywgUGFzc3dvcmQ9W1BST1RFQ1RFRF0sIEVuYWJsZWQ9dHJ1ZSwgQWNjb3VudE5vbkV4cGlyZWQ9dHJ1ZSwgY3JlZGVudGlhbHNOb25FeHBpcmVkPXRydWUsIEFjY291bnROb25Mb2NrZWQ9dHJ1ZSwgR3JhbnRlZCBBdXRob3JpdGllcz1bUk9MRV9BRE1JTl1dIiwiZXhwIjoxNjM0MzYyMzIxLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl19.e4-tJzixAkOk9xeoX9zkehYjaH_DmBPe9tho6cCtu6M'
响应
{
"success": false,
"code": 403,
"msg": "Access is denied"
}
源代码:https://github.com/jkxyx205/spring-security-learn/tree/master/spring-security-token