美文网首页Oauth2JAVA
SpringBoot2.0 + Oauth2 实现RESTful

SpringBoot2.0 + Oauth2 实现RESTful

作者: Bertram_Wang | 来源:发表于2019-05-22 20:05 被阅读150次

    REST全称是Representational State Transfer; REST指的是一组架构约束条件和原则。有兴趣了解的朋友参考RESTful 架构详解;

    OAuth2

    网页翻译后截图

    配置授权服务oauth

    @EnableAuthorizationServer: 用于激活OAuth 2.0授权服务器。
    主要配置: 客户端信息, 管理令牌, 授权类型。
    实现接口: AuthorizationServerConfigurer。
    或者继承AuthorizationServerConfigurerAdapter。
    其实查看AuthorizationServerConfigurerAdapter发现其本身就实现了AuthorizationServerConfigurer。

    void configure(AuthorizationServerSecurityConfigurer security) throws Exception;
    
    void configure(ClientDetailsServiceConfigurer clients) throws Exception;
    
    void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;  
       
    

    具体配置例子:

    /**
     * @Date 2019年5月21日
     * @Sgin AuthorizationServerConfig--认证服务核心配置
     * @Author Bertram.Wang
     */
    @Configuration
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Resource(name = "dsOauth")
        DataSource dsOauth;
        @Autowired
        RedisConnectionFactory connectionFactory;
        @Autowired
        MyUserDetailsService userDetailsService;
        @Autowired
        AuthenticationManager authenticationManager;
        /**
         * <p>设置令牌存储方式</p>
         * InMemoryTokenStore 在内存中存储令牌。
         * RedisTokenStore 在Redis缓存中存储令牌。 
         * JwkTokenStore 支持使用JSON Web Key (JWK)验证JSON Web令牌(JwT)的子Web签名(JWS)
         * JwtTokenStore 不是真正的存储,不持久化数据,身份和访问令牌可以相互转换。
         * JdbcTokenStore 在数据库存储,需要创建相应的表存储数据
         */
        @Bean
        public TokenStore tokenStore() {
            return new RedisTokenStore(connectionFactory);
        }
        /**
         * <p>设置密码校验器</p>
         * NoOpPasswordEncoder 直接文本比较  equals
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        } 
        /**
         * *用来配置令牌端点(Token Endpoint)的安全约束。
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.allowFormAuthenticationForClients();//允许客户表单认证
            security.passwordEncoder(passwordEncoder());//设置oauth_client_details中的密码编码器
            security.checkTokenAccess("permitAll()");//对于CheckEndpoint控制器[框架自带的校验]的/oauth/check端点允许所有客户端发送器请求而不会被Spring-security拦截
        }
    
        /**
         * *配置OAuth2的客户端相关信息。使用了数据库存储
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // jdbcTemplate 会查询指定数据源表: oauth_client_details;
            clients.jdbc(dsOauth);
        }
    
        /**
         * *配置授权服务器端点的属性和增强功能。
         * *设置自定义验证规则, token存储设置使用...
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                     .tokenStore(tokenStore())
                     .userDetailsService(userDetailsService)
                     // refreshToken是否可以重复使用。 默认:true;
                     .reuseRefreshTokens(false);
        }
    }
    

    注意:

    • 需要设置密码检验器;在用户创建时,需要注意密码密文的存储。
    • 身份验证管理升级后不能直接注入。需要添加一下Bean
    @Configuration
    public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {   
        @Bean(name = "authenticationManager")
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        } 
    } 
    
    • 刷新token时,refreshToken有效时间更新,实现一次登录一直使用。

      reuseRefreshTokens 设置为false。
      
    • MyUserDetailsService:自定义的身份验证逻辑实现接口 UserDetailsService
      示例:

    /**
     * @Date 2019年5月21日
     * @Sgin MyUserDetailsService
     * @Author Bertram.Wang
     */
    @Component
    public class MyUserDetailsService implements UserDetailsService {
        private static final Logger log = LoggerFactory.getLogger(MyUserDetailsService.class);
    
        @Autowired
        private MemberRepository memberRepository;
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            String clientFlagValue;
            String name;
            try {
                // username 使用客户端ID凭借 == clientId:username
                String[] split = username.split(":");
                clientFlagValue = split[0];
                name = split[1];
            } catch (Exception e) {
                log.error("用户名请拼接资源服务标识");
                return null;
            }
            
            MyUserDetails userDetails = new MyUserDetails();
            userDetails.setUsername(username);
            // 根据标识区分具体的客户端
            OauthClientFlagEnum clientFlagEnum = OauthClientFlagEnum.build(clientFlagValue);
            switch (clientFlagEnum) {
            case VUE_WEB:
                User user = userRepository.oneByName(name);
                if (user == null) {
                    throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
                }
                userDetails.setUserId(user.getId().toString());
                userDetails.setPassword(user.getPassword());
                break;
            case MEMBER_API:
                Member member = memberRepository.oneByPhone(name);
                if (member == null) {
                    throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
                }
                userDetails.setUserId(member.getId().toString());
                userDetails.setPassword(member.getPassword());
                break;
            default:
                log.error("用户名请拼接资源服务标识==clientFlagValue:{}", clientFlagValue);
                return null;
            }
            return userDetails;
        }
        /**
         *  *用于存储用户信息
         */
        @Data
        private final static class MyUserDetails implements UserDetails {
            private static final long serialVersionUID = 1L;
            private String username;
            private String password;
            private String userId;
            private MyUserDetails() {
                super();
            }
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                Role role = new Role();
                return role.getRoles();
            }
            @Override
            public boolean isAccountNonExpired() {
                return true;
            }
            @Override
            public boolean isAccountNonLocked() {
                return true;
            }
            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }
            @Override
            public boolean isEnabled() {
                return true;
            }
        }
    }
    
    • 客户端表
    CREATE TABLE `oauth_client_details` (
      `client_id` varchar(48) NOT NULL,
      `resource_ids` varchar(256) DEFAULT NULL,
      `client_secret` varchar(256) DEFAULT NULL,
      `scope` varchar(256) DEFAULT NULL,
      `authorized_grant_types` varchar(256) DEFAULT NULL,
      `web_server_redirect_uri` varchar(256) DEFAULT NULL,
      `authorities` varchar(256) DEFAULT NULL,
      `access_token_validity` int(11) DEFAULT NULL,
      `refresh_token_validity` int(11) DEFAULT NULL,
      `additional_information` varchar(4096) DEFAULT NULL,
      `autoapprove` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`client_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    客户端服务

    首先配置所有连接都可以访问。

    @Configuration
    // 注解开启Spring Security的功能
    @EnableWebSecurity 
    // 开启自动配置
    @EnableAutoConfiguration
    // 启用了一个Oauth2 客户端配置
    @EnableOAuth2Client
    public class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable() // 禁用csrf支持
                // 允许使用 HttpServletRequest 限制访问
                .authorizeRequests() 
                // 任何人都可以使用任何URL资源
                .antMatchers("/**").permitAll();
        }
    }
    

    添加自定义拦截器验证token

    /**
     * @describe 拦截器 -- oauth验权
     * @author Bertram.Wang
     * @date 2018年10月16日 下午8:58:17
     */
    public class Oauth2Interceptor implements HandlerInterceptor {
    
        public static final Logger LOGGER = LoggerFactory.getLogger(Oauth2Interceptor.class);
        
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            String accessToken;
            String authorization = request.getHeader("Authorization");
            if (StringUtils.isEmpty(authorization)) {
                ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
                return false;
            }
            try {
                accessToken = new String(Base64.getDecoder().decode(authorization)).split(":")[1];
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
                ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
                return false;
            }
            
            OAuth2AccessToken oauth2AccessToken = Oauth2Utils.checkTokenInOauthClient(accessToken);
            
            if (oauth2AccessToken == null) {// 非法的Token值
                ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
                return false;
            } else if (oauth2AccessToken.isExpired()) {// token失效
                ResponseUtils.writeResponseData(response, fail(MyReponseStatus.AUTH_LACK));
                return false;
            }
            
            Map<String, Object> additionalInformation = oauth2AccessToken.getAdditionalInformation();
            int memberId = Integer.parseInt(additionalInformation.get("userId").toString());
            request.setAttribute("memberId", memberId);
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
        }
    
        protected static String extractTokenKey(String value) {
            if (value == null) {
                return null;
            }
            MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).");
            }
    
            try {
                byte[] bytes = digest.digest(value.getBytes("UTF-8"));
                return String.format("%032x", new BigInteger(1, bytes));
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException("UTF-8 encoding not available.  Fatal (should be in the JDK).");
            }
        }
    
    }
    

    注册拦截器

    /**
     * @describe WEBMVC配置
     * @author Bertram.Wang
     * @date 2018年10月16日 下午8:56:49
     */
    @Configuration
    public class InterceptorRegisterConfiguration implements WebMvcConfigurer{
    
        @Bean
        public Oauth2Interceptor oauth2Interceptor() {
            return new Oauth2Interceptor();
        }
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(oauth2Interceptor())
            // 排除指定连接不做拦截
            .excludePathPatterns("/swagger-resources/**", 
                                 "/webjars/**", 
                                 "/swagger-ui.html/**", 
                                 "/docs", 
                                 "/account/**", 
                                 "/auth/**" );
        }
    }
    

    完成OAuth身份验证。

    相关文章

      网友评论

        本文标题:SpringBoot2.0 + Oauth2 实现RESTful

        本文链接:https://www.haomeiwen.com/subject/jqxuzqtx.html