述
上文中,了解了 Spring Security OAuth 的一个大致的流程,本文将我们之前写的用户名密码登录的方式重新修改一下,让他登录之后也返回一个token, 后续请求都通过token来请求
实现思路
回顾一下之前的流程图:     
我们在用户名密码等登陆认证完成之后,会生成一个已认证的 Authentication 对象, 看一下图中的 Authentication 这里,也就是说我们再搞到一个 OAuth2Request 对象,就可以用他后面的逻辑去处理生成了,大致流程图如下   
这里就是在登录成功之后,在登录成功的处理器里面,从请求中获取到basic client的信息,然后去获取 ClientDetails 的信息, 虚线框住的部分是需要我们自己去实现的  
具体实现
我们最终是希望拿到一个 OAuth2Request 对象, 如图,得先通过 ClientDetailsService 获取 ClientDetails ,所以第一步得先从请求中把 ClientId 拿出来   
之前我们的请求头中有 Authorization:Basic dGVzdEFwcGlkOnRlc3RTZWNyZXQ= 我们只要把这个拿出来做解析就可以拿到 clientId 了  
BasicAuthenticationFilter 中有一段解析的代码, 如下:   
具体的解码方法代码如下:   
把这两段代码直接复制到我们的成功处理器里面,然后做一些修改即可,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80@Slf4j
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler  extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;
    @Autowired
    private SecurityProperties securityProperties;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("用户登录成功...");
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请在header中传入client信息");
        }
        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;
        String clientId = tokens[0];
        String clientSecret = tokens[1];
        // 拿到client的信息之后,去构建clientDetails
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        // 验证
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId错误");
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配");
        }
        // 创建 TokenRequest对象
        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
        // 返回
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(accessToken));
    }
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
            throws IOException {
        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        }
        catch (IllegalArgumentException e) {
            throw new BadCredentialsException(
                    "Failed to decode basic authentication token");
        }
        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] { token.substring(0, delim), token.substring(delim + 1) };
    }
}
这里主要是先获取 client 的信息,然后一步一步往下创建对象就可以了
资源服务器的配置
现在项目里的一些登录路径什么的都没有做配置,所以在资源服务器的配置类里还需要做一些配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65@Configuration
@EnableResourceServer
public class MyResourcesServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig;
    @Autowired
    private ValidateCodeSecurityConfig validateCodeSecurityConfig;
    @Autowired
    private SpringSocialConfigurer securitySocialConfigurer;
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    
    @Autowired
    private AuthenticationFailureHandler myAuthenticationFailureHandler;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // web网页登录的配置
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler);
        http
                .apply(validateCodeSecurityConfig)
                .and()
                .apply(smsAuthenticationSecurityConfig)
                .and()
                .apply(securitySocialConfigurer)
                .and()
                .authorizeRequests()
                // 匹配的是登录页的话放行
                .antMatchers(
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*",
                        securityProperties.getBrowser().getLoginPage(),
                        SecurityConstants.DEFAULT_SIGN_UP_URL,
                        securityProperties.getBrowser().getSignUpPage(),
                        SecurityConstants.GET_SOCIAL_USER_URL,
                        securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
                        "/user/register"
                )
                .permitAll()
                // 授权请求. anyRequest 就表示所有的请求都需要权限认证
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
        ;
    }
}
直接先把浏览器的配置拷过来,然后去掉一些浏览器特有的配置,后期再改
测试
启动项目,访问用户名密码登录的接口 /authentication/form ,效果如下:   
可以看到,这里已经成功返回token了
短信验证码登录
如果之前的验证码是放到 redis 中的话,短信登录现在也是可以正常使用了的,如果是使用的 session 的方式,那还需要做一些修改,因为在App的请求中是没有cookie的,也就没有JSESSIONID, 也就获取不到session, 具体修改这里就不说了