springcloud9

密码模式获取token

postman

  • 测试访问:localhost:7070/auth/oauth/token,post请求
    • body(form-data)
      • grant_type = password
      • scope = all (匹配的字符串,存储在认证服务器中,也是在代码中配置的)
      • username = James (平台用户)
      • password = 123456 (平台用户)
    • Authorization,Basic Auth,Base64加密,Base64(id:password),把加密完的信息放到请求头,通过按一下"Preview Request"就可以实现
      • Username = pc
      • Password = 123456

特点

  • 粒度比客户端更细,客户端模式下一个客户端就只有一个token,不能区分用户,而密码模式是每个用户都有单独的token

micro-security-db

AuthorizationServerConfiguration
  • package com.xiangxue.jack.config;
    
    import com.xiangxue.jack.service.UserServiceDetail;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.ClientDetailsService;
    import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
    import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
    
    import javax.sql.DataSource;
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        private TokenStore tokenStore;
    
        @Autowired
        private UserServiceDetail userServiceDetail;
    
        @Autowired
        private ClientDetailsService clientDetailsService;
    
        static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
    
        // 使用数据库存储
        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }
    
        @Bean // 声明 ClientDetails实现
        public ClientDetailsService clientDetailsService() {
            return new JdbcClientDetailsService(dataSource);
        }
    
    	
        // 类似spring的钩子方法,springmvc的转换器、自己的过滤器,才能放到spring的容器中去
        // 这里就是oauth2.0的钩子方法
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(clientDetailsService);
        }
    
        // 也是一个钩子方法
       
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // redisTokenStore
    //        endpoints.tokenStore(new MyRedisTokenStore(redisConnectionFactory))
    //                .authenticationManager(authenticationManager)
    //                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    
            // 存数据库
            // tokenStore是用来存储token的
            // 1.声明了token的存储方式
        	// 2.定义了权限校验的管理器
        	// 3.定义了用户校验的service
            endpoints.tokenStore(tokenStore)
                    .authenticationManager(authenticationManager)
                    .userDetailsService(userServiceDetail);
    
            // 配置tokenServices参数
            // tokenServices是用来控制token属性的
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setTokenStore(endpoints.getTokenStore());
            //支持refreshtoken
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
            tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
            tokenServices.setAccessTokenValiditySeconds(60 * 5);
            //重复使用
            tokenServices.setReuseRefreshToken(false);
            tokenServices.setRefreshTokenValiditySeconds(60 * 10);
            endpoints.tokenServices(tokenServices);
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // 允许表单认证
            security.allowFormAuthenticationForClients()
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()");
        }
    
    }
    
    
SecurityConfiguration
  • package com.xiangxue.jack.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        // 密码加密
        // postman传的明明是明文,但是数据库存的是密文
        @Bean
        PasswordEncoder passwordEncoder() {
            return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            AuthenticationManager manager = super.authenticationManagerBean();
            return manager;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.requestMatchers().anyRequest()
                    .and()
                    .authorizeRequests()
    //                .antMatchers("/oauth/**").permitAll();
                    .antMatchers("/").authenticated();
    
    /*        http.csrf().disable().exceptionHandling()
                    .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                    .and()
                    .authorizeRequests().
                    antMatchers("/favicon.ico").permitAll()
                    .antMatchers("/oauth/**").permitAll()
                    .antMatchers("/login/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .and()
                    .httpBasic().disable();*/
            http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();  //拦截所有请求 通过httpBasic进行认证
    
        }
    }
    
    
UserServiceDetail
  • package com.xiangxue.jack.service;
    
    import com.xiangxue.jack.dao.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceDetail implements UserDetailsService {
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return userRepository.findByUsername(username);
        }
    }
    
    
    • UserDetailsService是oauth2.0中的接口

micro-web-secutiry(鉴权客户端)

OAuth2ClientConfig
  • package com.xiangxue.jack.oauth2;
    
    import feign.RequestInterceptor;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
    import org.springframework.security.oauth2.client.OAuth2RestTemplate;
    import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
    
    /*
      鉴权过滤器
    * @  OAuth2AuthenticationProcessingFilter
    *
    * */
    @EnableOAuth2Client
    @EnableConfigurationProperties
    @Configuration
    public class OAuth2ClientConfig {
    
    
        @Bean
        @ConfigurationProperties(prefix = "security.oauth2.client")
        public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
            return new ClientCredentialsResourceDetails();
        }
    
    //    @Bean
        public RequestInterceptor oauth2FeignRequestInterceptor(ClientCredentialsResourceDetails clientCredentialsResourceDetails) {
            return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails);
        }
    
        @Bean
        public OAuth2RestTemplate clientCredentialsRestTemplate() {
            return new OAuth2RestTemplate(clientCredentialsResourceDetails());
        }
    }
    
    
    • @EnableOAuth2Client
ResourceServerConfiguration
  • package com.xiangxue.jack.oauth2;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    
    @Configuration
    @EnableResourceServer
    //启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
    //    @Autowired
    //    private TokenStore tokenStore;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            // 配置order访问控制,必须认证后才可以访问
            // "/order/**","/user/**"路径的需要权限校验,其余的不需要
            http.authorizeRequests()
                    .antMatchers("/order/**","/user/**").authenticated();
        }
    
        /*
        * 把token验证失败后,重新刷新token的类设置到 OAuth2AuthenticationProcessingFilter
        * token验证过滤器中
        * */
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
            resources.authenticationEntryPoint(new RefreshTokenAuthenticationEntryPoint());
    //        resources.tokenStore(tokenStore);
        }
    }
    
    
    • //启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
      @EnableGlobalMethodSecurity(prePostEnabled = true)

      • @PreAuthorize("hasRole('ROLE_ADMIN')")
        @RequestMapping("/queryUser")
        public List<ConsultContent> queryUser(HttpServletRequest request) {
            request.getCookies();
            Enumeration<String> headerNames = request.getHeaderNames();
            while(headerNames.hasMoreElements()) {
                String headername = headerNames.nextElement();
                log.info("header name = >" + headername + "= >" + "headervalue = " + request.getHeader(headername));
            }
            return userService.queryContents(request);
        }
        
      • @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”),在方法调用之前进行角色校验

配置文件
  • #security.basic.enabled=true
    #spring.security.user.name=micro-web
    #spring.security.user.password=123
    #security.oauth2.resource.jwt.key-uri=http://127.0.0.1:3030/oauth/token_key
    #security.oauth2.resource.service-id=api-gateway
    
    #这个是必配的,用来进行token校验,其它的可配可不配
    security.oauth2.resource.user-info-uri=http://127.0.0.1:7070/auth/security/check
    security.oauth2.resource.prefer-token-info=false
    #security.oauth2.client.id=micro-web
    security.oauth2.client.clientId=micro-web
    security.oauth2.client.client-secret=123456
    security.oauth2.client.access-token-uri=http://api-gateway/auth/oauth/token
    security.oauth2.client.grant-type=client_credentials
    security.oauth2.client.scope=all
    
    • 在下游系统之前有一个filter,zuul去调下游系统时会被filter拦截,会往认证服务器去校验token,就是上面标示的必配内容—security.oauth2.resource.user-info-uri=http://127.0.0.1:7070/auth/security/check,请求又会被认证服务器的filter拦截,进行token校验,如果校验通过就会访问必配的请求url,就会把token从数据库查询到的信息返回给下游系统
      • token会查询这张表—oauth_client_token表,同时会把用户相关的角色信息也一起返回回去
测试
  • 把github中的api-gateway.properties中的路由配置信息修改成密码模式下的module

  • 依次启动Eureka、MicroConfigServer、MicroSecurityDb(认证服务器)、MicroZuulSecurity、MicroWebSecurity、MicroOrderSecurity

  • 测试访问:localhost:7070/auth/oauth/token,post请求

    • body(form-data)
      • grant_type = password
      • scope = all (匹配的字符串,存储在认证服务器中,也是在代码中配置的)
      • username = James (平台用户)
      • password = 123456 (平台用户)
    • Authorization,Basic Auth,Base64加密,Base64(id:password),把加密完的信息放到请求头,通过按一下"Preview Request"就可以实现
      • Username = pc
      • Password = 123456
  • localhost:7070/web/user/query/queryUser可以访问成功,但是localhost:7070/web/user/query/bb访问失败,因为配置的权限是ROLE_ADMIN,但是第二个接口的权限是@PreAuthorize(“hasRole(‘ROLE_USER’)”)

授权码模式

  • 先获取授权码,然后再通过授权码去获取token

postman

获取code
  • 测试访问:localhost:7070/auth/oauth/authorize?client_id=pc&response_type=code&redirect_url=http://localhost:8083/login/callback,get请求
    • params
      • client_id= pc
      • response_type= code
      • redirect_url= http://localhost:8083/login/callback
  • 在浏览器中访问后,会弹出输入框,需要输入账号和密码,而账号和密码就是user表中的配置,输入账号jack和密码123456,就会进入授权码默认页面,是或授权获取,点击授权按钮后会自动附上授权码并调用写入的回调地址
根据code获取token
  • 测试访问:localhost:7070/auth/oauth/token,post请求

    • body(form-data)
      • grant_type = authorization_code
      • redirect_url = localhost:8083/login/callback (与获取code的回调地址保持一致)
      • code = te2iN4
    • Authorization,Basic Auth,Base64加密,Base64(id:password),把加密完的信息放到请求头,通过按一下"Preview Request"就可以实现
      • Username = pc
      • Password = 123456
  • 注意通过code只能拿一次token,再调用就获取不到了,测试时,在micro-web-security项目中,已经配置有localhost:8083/login/callback这个项目,会自动拼接号根据code获取token的http调用,所以这里手动再通过postman调用是不需要的

  • 注意上一步通过浏览器访问的,有手动确认的过程,可以在oauth_client_details表中配置自动确认,就不需要浏览器上手动确认了

安全校验总结

客户端模式

  • 有一些客户端访问并不需要客户登陆,运行游客登陆,不需要注册的这种,就可以使用客户端模式

密码模式

  • 比客户端模式控制粒度更细,获取token是跟用户绑定的,每一个用户都有一个token,根据token可以获取到用户信息,根据用户信息可以获取用户角色
    • 存在风险,如果客户端密码被盗

授权码模式

  • 回调时需要有授权码,安全性更高,即回调地址是跟用户绑定的,先去获取授权码,再通过授权码去获取token

缺点

  • 这三种模式都有一个缺点,多了一个下游系统和认证服务器的权限校验过程(通信交互),如果认证服务器挂了,整个系统就不可用了
    • security.oauth2.resource.user-info-uri=http://127.0.0.1:7070/auth/security/check这是校验token的配置,这里的域名和端口不能用服务名代替,只能配ip,可以用域名服务器做转发,并且配置监控keepalive的脚本,就是ps -ef看进程是否在正常运行

总结

  • token验证的过滤器OAuth2AuthenticationProcessingFilter

对oauth改造-----token过期怎么处理

  • 一般常规的做法是写定时器刷新token

  • package com.xiangxue.jack.oauth2;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.*;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
    import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
    import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
    import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.client.RestTemplate;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Map;
    
    public class RefreshTokenAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
    
        @Autowired
        private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
    
        private WebResponseExceptionTranslator<?> exceptionTranslator = new DefaultWebResponseExceptionTranslator();
    
        @Autowired
        RestTemplate restTemplate;
    
        private static String oauth_server_url = "http://micro-security-db/oauth/token";
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            try {
                //解析异常,如果是401则处理
                ResponseEntity<?> result = exceptionTranslator.translate(authException);
                if (result.getStatusCode() == HttpStatus.UNAUTHORIZED) {
                    MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
                    formData.add("client_id", clientCredentialsResourceDetails.getClientId());
                    formData.add("client_secret", clientCredentialsResourceDetails.getClientSecret());
                    formData.add("grant_type", clientCredentialsResourceDetails.getGrantType());
                    formData.add("scope", String.join(",",clientCredentialsResourceDetails.getScope()));
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                    Map map = restTemplate.exchange(oauth_server_url, HttpMethod.POST,
                            new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
                    //如果刷新异常
                    if (map.get("error") != null) {
                        // 返回指定格式的错误信息
                        response.setStatus(401);
                        response.setHeader("Content-Type", "application/json;charset=utf-8");
                        response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
                        response.getWriter().flush();
                        //如果是网页,跳转到登陆页面
                        //response.sendRedirect("login");
                    } else {
                        //如果刷新成功则存储cookie并且跳转到原来需要访问的页面
    
                        for (Object key : map.keySet()) {
                            response.addCookie(new Cookie(key.toString(), map.get(key).toString()));
                        }
                        request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
    //                    response.sendRedirect(request.getRequestURI());
    
                        //将access_token保存
                    }
                } else {
                    //如果不是401异常,则以默认的方法继续处理其他异常
                    super.commence(request, response, authException);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
    • OAuth2AuthenticationEntryPoint.commence是校验token发生异常catch后会调用的方法,因此新增一个类,并继承OAuth2AuthenticationEntryPoint重新commence方法
    • 这样就可以无缝刷新token
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值