Spring Security 生成token(十三)

说明

前后端分离的项目,需要后端认证成功之后,颁发令牌 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