26.SpringSecurity-重构用户名密码登陆

前言

image.png

  1. 咱们上一节对获取令牌最终产生令牌的流程有了必定了解。 那么在咱们本身的业务场景,本身写的登陆这个流程里面;咱们借助于TokenEndPoint到TokenGranter是不能用的。由于上面发起的是获取令牌的请求,而咱们是发起的登陆请求。
  2. 咱们要用咱们本身的过滤器去处理登陆请求,上面一直到TokenGranter这一步使用4种方式去去生成咱们的令牌。咱们都是不能用的,咱们须要用的就是令牌的服务产生令牌;

image.png
3.那么在哪里去运用上面AuthorizationServerTokenServices呢?是在咱们的:AuthenticationSuccessHandler里面。以下图:
image.png前端

4.咱们以前在spring-security-web项目里面写过AuthenticationSuccessHandler实现类的处理逻辑:若是是json登陆,咱们吧登陆成功信息authentication直接以json形式返回web

@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        /**
         * 将Authentication转换成json返回给前端
         * 参数:authentication 使用不一样登陆方式,其值是不同的,这是一个接口在实际运转中,他会传不一样的实现对象过来
         */
        logger.info("登陆成功");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){//JSON异步登陆
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {
            //非json就是使用父类处理器---父类处理器就是跳转
            super.onAuthenticationSuccess(request,response,authentication);
        }
    }
}

在app端时候,咱们在登陆认证成功以后,咱们返回的再也不是用户的认证信息,而是:用TokenServices生成的OAuth2AccessToken;从上一节咱们知道,咱们若是要生成OAuth2AccessToken就须要OAuth2Authentication;生成OAuth2Authentication咱们又须要OAuth2Request和Authentication。以前在Spring Security OAuth中是根据TokenGranter的不一样实现模式(根据传递的grant_type)来构建生成OAuth2Authentication咱们又须要OAuth2Request和Authentication。可是在咱们登陆成功处理器里面的方法参数上就有一个:Authentication authentication;因此在咱们的代码逻辑里面是不须要建立Authentication的业务逻辑的(因其在登陆时候就已经建立好了),咱们只须要建立处理OAuth2Request。spring

  1. OAuth2Request的构建成了咱们建立OAuth2AccessToken的关键。从上一节知道咱们建立OAuth2Request须要参数:ClientDetails和TokenRequest。ClientDetails从ClientDetailsService根据ClientId获取。ClientId从哪里来?从咱们请求参数里面Header的Authorization里面来。咱们经过解析请求头中Authorization对应的字符串是能够拿到ClientId的。 TokenRequest是根据ClientDetails和请求paramaters来new出一个对象的。

内容

前言总结三点:json

  1. 写代码的位置在哪?在咱们的MyAuthenticationSuccessHandler的onAuthenticationSuccess里面。
  2. 最终目标是什么?最终目标是构建出:OAuth2Request进而构建出OAuth2AccessToken
  3. 入手源头在哪?入手源头是从:请求参数里面获取clientId。

1.解析请求头里面的clientId

咱们参考Spring Security OAuth2咱们知道,咱们使用BasicAuthenticationFilter解析 安全

image.png

MyAuthenticationSuccessHandler代码:服务器

@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        /**
         * 将Authentication转换成json返回给前端
         * 参数:authentication 使用不一样登陆方式,其值是不同的,这是一个接口在实际运转中,他会传不一样的实现对象过来
         */
        logger.info("登陆成功");

        //1.解析clientId
        String header = request.getHeader("Authorization");
        if (header == null && !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无clientId信息");
        }
        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;
        String clientId = tokens[0];
        String clientSecret = tokens[1];


        //2.获取ClientDetails并做校验
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if(null == clientDetails){//说明咱们根据配置的yxm拿不到第三方ClientDetails信息。咱们应该抛出异常
          throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:"+clientId);
        }else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){//若是clientDetails存在,咱们就应该校验clientSeret
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:"+clientSecret);
        }

        //3.获取TokenRequest:第一个参数map主要是为了建立Authentication;可是上面Authentication已经建立。咱们能够将其设置为空
        //由于grantType为:受权类型 以前是:password、authentication_code、implit、Client Credential、咱们这里为自定义模式
        /**
         * 类:ResourceOwnerPasswordTokenGranter
         *
         * protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
         *         Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
         *         String username = (String)parameters.get("username");
         *         String password = (String)parameters.get("password");
         *         parameters.remove("password");
         *         Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
         * }
         */
        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId, clientDetails.getScope(), "custom");

        //4.建立OAuth2Request
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        //5.建立OAuth2Authentication
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        //6.响应返回
        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 var7) {
            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");
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
}

2.安全配置

有了上面其实仍是不够的,资源服务器目前只是用了Spring Security Oauth2的默认配置。以前咱们写spring-security-web项目的时候的安全配置:WebSecurityConfig主配置类中,全部的配置都是在configure里面的。如今同理咱们也须要一个配置。也就是咱们注解:@EnableResourceServer修饰的类。app

@Configuration
@EnableResourceServer
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; //短信验证码受权配置
    @Autowired
    private SpringSocialConfigurer mySocialSecurityConfig;
    @Autowired
    private SecurityProperties securityProperties;


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler);

        http.apply(smsCodeAuthenticationSecurityConfig)
                .and()
                .apply(mySocialSecurityConfig)//配置第三方social
                .and()
                .authorizeRequests()
                .antMatchers(
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                        securityProperties.getBrowser().getLoginPage(),
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
                        securityProperties.getBrowser().getSignUpUrl())
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();
    }
}

3.启动项目 测试验证

在spring-security-demo项目咱们以前是访问:"/authentication/form"来访问登陆的。咱们专门写了页面,如今咱们使用app端登陆,咱们使用:restlet_client提交请求。咱们书写用户名和密码。
组织请求参数:
image.png异步

image.png

咱们携带这个access_token去访问用户信息:ide

{
 "access_token": "4f04305c-a956-4546-95ba-20ff8d9ed2e3",
 "token_type": "bearer",
 "refresh_token": "76abf98d-dbe5-4f2e-8f18-cf83c6f3a2c5",
 "expires_in": 43199
}

image.png

4.总结

其实咱们如今已经达到咱们的要求了。app去调用咱们自个表单登陆的服务请求。而后给他返回一个OAuth2AccessToken。而后他拿着这个Access_Token再去请求相应的资源。测试

相关文章
相关标签/搜索