重构用户名密码登录获取token

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

用户名密码登录获取token

WhaleAuthenticationSuccessHandler

AuthenticationSuccessHandler
在成功处理器中根据请求头解析出client-id
在这里插入图片描述
参考 org.springframework.security.web.authentication.www.BasicAuthenticationFilter#doFilterInternal

@Component("whaleAuthenticationSuccessHandler")
public class WhaleAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());

	// ObjectMapper spring mvc 提供的
	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private SecurityProperties securityProperties;

	@Autowired
	private ClientDetailsService clientDetailsService;

	@Autowired
	private  AuthorizationServerTokenServices authorizationServerTokenServices;


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.security.web.authentication.
	 * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
	 * HttpServletRequest, javax.servlet.http.HttpServletResponse,
	 * org.springframework.security.core.Authentication)
	 */
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
										Authentication authentication) throws IOException, ServletException {

		logger.info("登录成功");

		String header = request.getHeader("Authorization");

		if (header == null || !header.toLowerCase().startsWith("basic ")) {
//			chain.doFilter(request, response);
			throw new UnapprovedClientAuthenticationException("请求头中无client信息");
//			return;
		}
//		try {
		String[] tokens = extractAndDecodeHeader(header, request);
		assert tokens.length == 2;

		String username = tokens[0];

		String clientId = tokens[0];
		String clientSecret = tokens[1];

		//拿到了clientDetails
		ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        //校验
		if(clientDetails==null){
			throw new UnapprovedClientAuthenticationException("client-id对应信息不存在:"+clientId);
		}else if(! StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
			throw new UnapprovedClientAuthenticationException("client-secret对应信息不匹配:"+clientSecret);
		}

		//不需要再构建authentication 传一个空的map
		//grantType 为四种授权模式  我们是自定义的
		TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");

		OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

		OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

		OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);



		response.setContentType("application/json;charset=UTF-8");
//		response.getWriter().write(objectMapper.writeValueAsString(authentication));
		response.getWriter().write(objectMapper.writeValueAsString(oAuth2AccessToken));

//		if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
//			response.setContentType("application/json;charset=UTF-8");
//			response.getWriter().write(objectMapper.writeValueAsString(authentication));
//		}else {
//			//如果配置的返回类型不是json,则调用父类方法,进行跳转
//			super.onAuthenticationSuccess(request,response,authentication);
//		}

	}

		/**
		 * Decodes the header into a username and password.
		 *
		 * @throws BadCredentialsException if the Basic header is not present or is not valid
		 * Base64
		 */
		private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
			throws IOException {

			byte[] base64Token = header.substring(6).getBytes("UTF-8");
			byte[] decoded;
			try {
				decoded = Base64.getDecoder().decode(base64Token);
			}
			catch (IllegalArgumentException e) {
				throw new BadCredentialsException(
						"Failed to decode basic authentication token");
			}

//			String token = new String(decoded, getCredentialsCharset(request));
			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) };
		}
}

WhaleResourceServerConfig

参考 BrowserSecurityConfig

@Configuration
@EnableResourceServer
public class WhaleResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    protected AuthenticationSuccessHandler whaleAuthenticationSuccessHandler;

    @Autowired
    protected AuthenticationFailureHandler whaleAuthenticationFailureHandler;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(whaleAuthenticationSuccessHandler)
                .failureHandler(whaleAuthenticationFailureHandler);

        http//.apply(validateCodeSecurityConfig)
               //.and()
                .apply(smsCodeAuthenticationSecurityConfig)
                .and()
               /*
               APP上没有rember的概念
               .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSecodes())*/
               /* .userDetailsService(userDetailsService)
                .and()
                .sessionManagement()

                .invalidSessionStrategy(invalidSessionStrategy)
                .maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
                .maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
                .expiredSessionStrategy(sessionInformationExpiredStrategy)*/

//                .invalidSessionUrl("/session/invalid")
//                .maximumSessions(1)//为1 后面登录的session会把前面的登录的session失效掉
//                .maxSessionsPreventsLogin(true)//当session数量达到最大时 阻止后面的登录
//                .expiredSessionStrategy(new MyExpiredSessionStrategy())//并发登录导致超时的处理策略
//                .and()
//                .and()
//                .logout()
//                .logoutUrl("/signOut")
//                .logoutSuccessUrl("/logOut.html")
//                .logoutSuccessHandler(LogoutSuccessHandler) //Handler和Url是互斥的
//                .deleteCookies("JESSIONID")//清除cookie中的 当前session id
//                .and()
                .authorizeRequests()
                .antMatchers(
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                        securityProperties.getBrowser().getLoginPage(),
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
                        securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".json",
                        securityProperties.getBrowser().getSession().getSessionInvalidUrl()+".html",
                        "/session/invalid")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .cors().disable().csrf().disable();// 禁用跨站攻击

    }
}

启动测试获取token

模拟表单登录获取token
在这里插入图片描述在这里插入图片描述

拿着token请求资源
在这里插入图片描述
直接浏览器访问会报错
在这里插入图片描述

重构短信登录获取token

测试用手机验证获取token

在这里插入图片描述

WhaleResourceServerConfig

在这里插入图片描述这个不能配置,因为如果加上了验证码校验的配置,表单获取token的时候也就需要验证码了,
继而不能请求发送手机短信的token,后面再说吧
解决
ValidateCodeFilter 注释掉表单登录的验证码验证

//        urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);

ok

拿着token请求发送手机短信验证码的接口

上次的token9995dfa3-f3c5-441d-9e3a-032ab62eec02
在这里插入图片描述
只需要在请求头上加上token就可以请求资源

在这里插入图片描述

postman用手机短信码去获取token及问题


我们的postman是模拟浏览器请求,会带上cookie,cookie里面又还有session id 可以找到session
但是如果我们用代码去发送请求,它是没有cookie也没有session 所有 会报一个短信验证码不存在的错误
因为我们的验证码是存在session里面的

所有app中我们不能用session去存储手机验证码

重构验证码存储逻辑

在这里插入图片描述

@Autowired

@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。

验证码存储 ValidateCodeRepository

在这里插入图片描述

ValidateCodeRepository

public interface ValidateCodeRepository  {


    /**
     * 保存验证码
     * @param request
     * @param validateCode
     * @param validateCodeType
     */
    void save(ServletWebRequest request ,ValidateCode validateCode,ValidateCodeType validateCodeType);


    /**
     * 获取验证码
     * @param request
     * @param validateCodeType
     * @return
     */
    ValidateCode get(ServletWebRequest request,ValidateCodeType validateCodeType);


    /**
     * 移除验证码
     * @param request
     * @param validateCodeType
     * @return
     */
    void remove(ServletWebRequest request,ValidateCodeType validateCodeType);
}

RedisValidateCodeRepository

app端我们把验证码存到redis里面
在这里插入图片描述

@Component
public class RedisValidateCodeRepository implements ValidateCodeRepository {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    /**
     * 保存验证码
     *
     * @param request
     * @param validateCode
     * @param validateCodeType
     */
    @Override
    public void save(ServletWebRequest request, ValidateCode validateCode, ValidateCodeType validateCodeType) {
        //存redis 30分钟超时时间
        redisTemplate.opsForValue().set(buildKey(request,validateCodeType),validateCode,30,TimeUnit.MINUTES);
    }

    /**
     * 获取验证码
     * @param request
     * @param validateCodeType
     * @return
     */
    @Override
    public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {
        Object value = redisTemplate.opsForValue().get(buildKey(request, validateCodeType));
        if(value==null){
            return null;
        }
        return (ValidateCode) value;
    }

    /**
     * 移除验证码
     *
     * @param request
     * @param validateCodeType
     * @return
     */
    @Override
    public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) {
        redisTemplate.delete(buildKey(request,validateCodeType));
    }

    private String buildKey(ServletWebRequest request, ValidateCodeType validateCodeType) {
        String deviceId = request.getHeader("deviceId");
        if(StringUtils.isBlank(deviceId)){
            throw new ValidateCodeException("请在请求头中携带deviceId参数");
        }
        return "code:"+validateCodeType.toString().toLowerCase()+":"+deviceId;

    }

    /**
     * 根据请求的url获取校验码的类型
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(ServletWebRequest request) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
        return ValidateCodeType.valueOf(type.toUpperCase());
    }
}
SessionValidateCodeRepository

浏览器端还是存到session中国

在这里插入图片描述

`@Component
public class SessionValidateCodeRepository implements ValidateCodeRepository {


    /**
     * 操作session的工具类
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 保存验证码
     *
     * @param request
     * @param validateCode
     * @param validateCodeType
     */
    @Override
    public void save(ServletWebRequest request, ValidateCode validateCode, ValidateCodeType validateCodeType) {
        ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime());
        sessionStrategy.setAttribute(request, getSessionKey(request), code);
    }

    /**
     * 获取验证码
     *
     * @param request
     * @param validateCodeType
     * @return
     */
    @Override
    public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {

        String sessionKey = getSessionKey(request);

        ValidateCode codeInSession = (ValidateCode) sessionStrategy.getAttribute(request, sessionKey);

        return codeInSession;
    }

    /**
     * 移除验证码
     *
     * @param request
     * @param validateCodeType
     * @return
     */
    @Override
    public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) {
        sessionStrategy.removeAttribute(request,getSessionKey(request));
    }


    /**
     * 构建验证码放入session时的key
     *
     * @param request
     * @return
     */
    private String getSessionKey(ServletWebRequest request) {
//		return SESSION_KEY_PREFIX + getProcessorType(request);
        return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase();

    }

    /**
     * 根据请求的url获取校验码的类型
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(ServletWebRequest request) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
        return ValidateCodeType.valueOf(type.toUpperCase());
     }

  }

`

AbstractValidateCodeProcessor 更换验证码存储接口

引入

	@Autowired
	private ValidateCodeRepository validateCodeRepository;

来对应操作验证码

在依赖app的时候就会找到我们的RedisValidateCodeRepository 的bean

redis配置

## Redis服务器地址
spring.redis.host=192.168.85.134
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=123456

测试

在这里插入图片描述在这里插入图片描述测试成功

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值