关于Security oauth2 +Sso整合出现的问题

当搭建好security授权服务器,资源服务器,和单点登录客户端的时候,挺开心的,基本上都是使用注解写的,也没啥多大的变动.

但是在我使用ResponseBodyAdvice包装类统一返回响应请求的时候,问题就来了,再进行统一包装的时候也会将通过授权后返回的token信息进行包装,可是当客户端对返回的response进行解析的时候死活解析不出来,会把返回的信息全部放入到增强内容additionallnformation当中

这就令我很苦恼,也算没吃透security就开始写带来的问题,找了很多方法,但是都没啥意义.

主要我的客户端是使用security提供的默认的配置,都不好进行改动,而自己又不想去另写客户端(这样还要去写单点登录).

解决的方法其实也有

1.配置security中的响应请求进行重写,然后放入到security config配置中(写了,但由于是使用默认配置,报错了 bean冲突)

2.删掉统一包装方法(暂时使用,因为反正是授权,不包装也可以)

3.跟1其实差不多,别使用默认的注解配置,自己去配置客户端,写sso代码,这样就不用局限于默认框架的拘束之下了.

Security身份认证顺序问题

另一个问题,当用户被封禁后isEnabled()方法为false的时候,用户isAccountNonLocked()同时被触发时,这里就会提前返回用户账户被锁定,源码中首先校验的是是否锁定,在AbstractUserDetailsAuthenticationProvider类中有默认的校验方案

 这就会让用户接受到不正确的信息,而这正是默认实现的DaoAuthenticationProvider实现的校验策略,所以我们要用自己的AuthenticationProvider,并且去自己实现用户身份校验.

AuthenticationSuccessEvent无法监听问题

今天又遇到一个问题,监听类收不到广播类推送的登录成功消息,先放出代码.

/**
 * 用户登录事件监听
 * 如果用户登录成功则在日志进行记录
 * 并且发送邮件告知用户登录,以及登录位置
 * 提醒end user账号被登录
 */
@Component
public class UserLoginListener {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private EmailUtil emailUtil;

    @EventListener(AuthenticationSuccessEvent.class)
    public void userLoginSuccessfulOnApplicationEvent(AuthenticationSuccessEvent event) throws MessagingException {

        //根据访问的请求ip查找用户的位置
        //http://whois.pconline.com.cn/?ip=123.91.217.123查询IP地址的地理位置
        String url = "http://whois.pconline.com.cn/?ip=" + this.getIpAddress();
        String result = this.getResult(url);

        int data = result.indexOf("位置:");
        int first = data + 3;
        int last = data + 20;
        String geographicLocation = result.substring(first, last).trim();
        System.out.println(geographicLocation);

        //拿到登录的用户信息
        Authentication authentication = event.getAuthentication();
        Object principal = authentication.getPrincipal();
        String userName = authentication.getName();

        String userEmail = null;

        //获取电子邮箱
        if (principal instanceof User) {
            User user = (User) principal;
            userEmail = user.getEmail();
        }

        String userRole = null;

        String bodyMessage = authentication.getName() + "  您好,您刚刚在  " + geographicLocation + "\n登录,如果这不是您" +
                "本人进行的操作,请赶紧修改密码或者冻结账号";

        Set<String> authorizeds = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
        for (String authorized : authorizeds) {
            if (authorized.equals("normal")) {
                userRole = "尊敬的用户   ";
                break;
            } else if (authorized.equals("root")) {
                userRole = "超级管理员管理员SAMA   ";
            } else if (authorized.equals("admin")) {
                userRole = "管理员SAMA   ";
            } else if (authorized.equals("CLIENT")) {
                userRole = "Sso客户端信息   \n";
                bodyMessage = authentication.getName() + "--> 服务器启动了,Sso客户端正在上线中";
            }
        }

        //邮件的提示信息
        String emailMessage = userRole + bodyMessage;
        String email = userEmail;
        //发送邮件
        Thread thread = new Thread(() -> emailUtil.loginUpEmail(userName, emailMessage, email));
        thread.start();

    }

监听的是AuthenticationSuccessEvent这个类,这个类很有意思,它的推送是由ProviderManager验证成功后推送的,之前修改了AuthenticationProvider,也没出现什么问题,但是今天使用的时候就开始整活,死活都监听不到消息,估计就是没推送,换其他事件都能监听到,直接打断点到ProviderManager,可以看到.

 private AuthenticationEventPublisher eventPublisher;

...............

if (result == null && this.parent != null) {
            try {
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            } catch (ProviderNotFoundException var12) {
            } catch (AuthenticationException var13) {
                parentException = var13;
                lastException = var13;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;


//Manager自己实现的推送方法
 private static final class NullEventPublisher implements AuthenticationEventPublisher {
        private NullEventPublisher() {
        }

        public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
        }

        public void publishAuthenticationSuccess(Authentication authentication) {
        }
    }

看完就明白了,啥玩意,搞个空的等于就推了空气,所以只需要自己在验证成功后自己手工去推一下就好了.

**
 * 自定义认证条件
 */
@Component
public class UserAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    protected final Log logger = LogFactory.getLog(this.getClass());

    @Autowired
    private UserService authUserDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private ApplicationEventPublisher publisher;

    /**
     * @Description 认证处理,返回一个Authentication的实现类则代表认证成功,返回null则代表认证失败
     * @Date 2019/7/5 15:19
     * @Version 1.0
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        //判断访问权限是否为空
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }


        String presentedPassword = authentication.getCredentials().toString();
        //创建UserDetails类
        UserDetails user = authUserDetailsService.loadUserByUsername(authentication.getName());

        //判断密码是否正确,如果不正确则抛出异常
        if (!this.passwordEncoder.matches(presentedPassword, user.getPassword())) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        //判断用户身份
        if (!user.isEnabled()) {
            this.logger.debug("Failed to authenticate since user account is disabled");
            throw new DisabledException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
        } else if (!user.isAccountNonLocked()) {
            this.logger.debug("Failed to authenticate since user account is locked");
            throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
        } else if (!user.isAccountNonExpired()) {
            this.logger.debug("Failed to authenticate since user account has expired");
            throw new AccountExpiredException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
        }

        //获取用户权限信息
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();

        /**
         * 一定要记住,如果没收到消息,就要自己实现,因为在ProviderManager里面它不会给你实现,而是使用
         * 自己空的方法去发布,等于放了个屁,然后就接收不到验证成功的信息,所以要自己实现一个
         */
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, presentedPassword, authorities);
        publisher.publishEvent(new AuthenticationSuccessEvent(usernamePasswordAuthenticationToken));
        return usernamePasswordAuthenticationToken;
    }

    /**
     * @Description 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
     * @Date 2019/7/5 15:18
     * @Version 1.0
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }

    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }


}

而且要注意,它接收的参数是Authentication authentication,可别直接就把一开始拿到的就放进去,那个只是身份验证,不能直接传进去,否则只能拿到账号和密码,必须要创建一个包装的方法,同时将其他的信息放入.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值