微服务SpringCloud项目(六):整合oauth并使用password密码认证模式

2,454 阅读8分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

📖前言

心态好了,就没那么累了。心情好了,所见皆是明媚风景。

“一时解决不了的问题,那就利用这个契机,看清自己的局限性,对自己进行一场拨乱反正。”正如老话所说,一念放下,万般自在。如果你正被烦心事扰乱心神,不妨学会断舍离。断掉胡思乱想,社区垃圾情绪,离开负面能量。心态好了,就没那么累了。心情好了,所见皆是明媚风景。

🚓引入依赖


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--freemarker,页面渲染引擎-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <!--  SpringBoot 监控客户端 -->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>${spring-boot-admin.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 引入数据库密码加密 -->
    <dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>${jasypt.version}</version>
    </dependency>

    <!-- 引入mysql链接依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql-connector}</version>
        <scope>provided</scope>
    </dependency>

    <!-- 引入Druid依赖
    java.sql.SQLFeatureNotSupportedException
    http://www.vmfor.com/p/101494868463.html-->
    <dependency>
        <!--自动配置-->
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>${common-pool.version}</version>
    </dependency>

    <!--<dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-micro-spring-boot-starter</artifactId>
        <version>${knife4j.version}</version>
    </dependency>-->

    <!-- 引入 MybatisPlus 依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>

    <!-- 引入多数据源依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>${mybatis-plus-dynamic.version}</version>
    </dependency>
    
</dependencies>

1. 启动类添加


@EnableFeignClients
//对外开启暴露获取token的API接口
@EnableResourceServer
@EnableDiscoveryClient

2. 创建一个授权的配置文件 AuthorizationServerConfig.java


根据官方指示我们需要创建一个配置类去实现 AuthorizationServerConfigurer 所以我们,创建一个类去继承它的实现类AuthorizationServerConfigurerAdapter,具体代码如下:

package com.cyj.dream.auth.config;

import com.cyj.dream.auth.entity.SysUser;
import com.cyj.dream.auth.persistence.service.impl.CustomUserServiceImpl;
import com.cyj.dream.core.constant.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
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.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 授权的配置文件
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.auth.config
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-30 
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
// 授权认证服务中心配置
@Configuration // 配置类
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private CustomUserServiceImpl userDetailsService;

    /**
     * accessToken 有效期 2小时
     */
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 7200 * 12 * 7;

    /**
     * accessToken 有效期 2小时
     */
    private static final int REFRESH_TOKEN_VALIDITY_SECONDS = 7200 * 12 * 7;

    /**
     * 定制化处理
     * 配置tokenStore的存储方式是redis存储
     * <p>
     * 这里存储在数据库,大家可以结合自己的业务场景考虑将access_token存入数据库还是redis
     *
     * @return
     */
    @Bean
    public TokenStore redisTokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        // redis key 前缀--DreamCloud
        tokenStore.setPrefix(Constant.tokenPrefix + "_");
        return tokenStore;
    }

    /**
     * 配置客户端的管理是jdbc
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("dream").secret(passwordEncoder.encode("c83fb51ff6e807e8805c6dd9d5707365"))
                // 授权码授权模式下的回调地址
                .redirectUris("http://www.baidu.com")
                .authorizedGrantTypes("authorization_code", "password", "refresh_token").scopes("all")
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                // 授权类型
                .refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
        //clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    /**
     * 认证服务器Endpoints配置
     *
     * @return
     * @author ChenYongJia
     * @date 2021/9/30
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
        endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET,
                HttpMethod.POST, HttpMethod.PUT,
                HttpMethod.DELETE)
                .tokenEnhancer(tokenEnhancerChain)
                //配置tokenStore管理、配置客户端详情--使用redis
                .tokenStore(redisTokenStore()).userDetailsService(userDetailsService)
                //配置授权模式
                .tokenGranter(tokenGranter(endpoints));
        //配置tokenServices的参数 +
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        //配置accessToken过期时间
        defaultTokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
        //配置refreshToken的过期时间
        defaultTokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
        //设置支持刷新token
        defaultTokenServices.setReuseRefreshToken(true);
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setTokenStore(endpoints.getTokenStore());
        defaultTokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        defaultTokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        endpoints.tokenServices(defaultTokenServices);
    }

    /**
     * 认证服务器相关接口权限管理
     *
     * @return
     * @author ChenYongJia
     * @date 2021/9/30
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //配置允许表单访问
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

    /**
     * 配置授权模式也可以添加自定义模式(不写也有默认的)
     * 具体可查看AuthorizationServerEndpointsConfigurer中的getDefaultTokenGranters方法
     * 以后添加一个手机验证码的功能
     *
     * @param endpoints
     * @return
     */
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> list = new ArrayList<>();
        //增加刷新token
        list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //授权码模式
        list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(), endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //客户端凭证模式
        list.add(new ClientCredentialsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //密码模式
        list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //隐藏式
        list.add(new ImplicitTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        return new CompositeTokenGranter(list);
    }

    /**
     * 创建一个token增强方法,增加一些我们自己想要返回的附加信息
     *
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>(2);
            additionalInfo.put("license", "SunnyChen-DreamChardonnay");
            SysUser user = (SysUser) authentication.getUserAuthentication().getPrincipal();
            if (user != null) {
                additionalInfo.put("userId", user.getSysUserId());
                additionalInfo.put("userPhone", user.getSysUserPhone());
                additionalInfo.put("userDeptId", user.getSysUserInfoDepartmentId());
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

    /**
     * 用来做验证
     *
     * @return
     */
    @Bean
    AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return daoAuhthenticationProvider().authenticate(authentication);
            }
        };
        return authenticationManager;
    }

    /**
     * 用来做验证
     *
     * @return
     */
    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return daoAuthenticationProvider;
    }

}

3. 由于授权服务器本身也是资源服务器,所以也创建一个资源配置,代码如下


package com.cyj.dream.auth.config;

import org.springframework.context.annotation.Configuration;
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;

/**
 * @Description: 资源配置器
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.auth.config
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-30
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Configuration
// 启用资源服务
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 配置资源接口安全,http.authorizeRequests()针对的所有url,但是由于登录页面url包含在其中,这里配置会进行token校验,校验不通过返回错误json,
     * 而授权码模式获取code时需要重定向登录页面,重定向过程并不能携带token,所有不能用http.authorizeRequests(),
     * 而是用requestMatchers().antMatchers(""),这里配置的是需要资源接口拦截的url数组
     *
     * @param http
     * @return void
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http    //配置需要保护的资源接口
                .requestMatchers().
                antMatchers("/user", "/test/need_token", "/logout", "/remove", "/update", "/test/need_admin", "/test/scope")
                .and().authorizeRequests().anyRequest().authenticated();
    }

}

4. 创建 webSecurity 配置


/*
     * OK ,关于这个配置我要多说两句:
     *
     * 1.首先当我们要自定义Spring Security的时候我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应
     * 方法即可。 2.我们在这里注册CustomUserService的Bean,然后通过重写configure方法添加我们自定义的认证方式。
     * 3.在configure(HttpSecurity http)方法中,我们设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,
     * 注销请求也是任何人都可以访问的。
     * 4.permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
     * 5.这里我们可以通过匹配器来匹配路径,比如antMatchers方法,假设我要管理员才可以访问admin文件夹下的内容,我可以这样来写:.
     * antMatchers("/admin/**").hasRole("ROLE_ADMIN"),也可以设置admin文件夹下的文件可以有多个角色来访问,
     * 写法如下:.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
     * 6.可以通过hasIpAddress来指定某一个ip可以访问该资源,假设只允许访问ip为210.210.210.210的请求获取admin下的资源,
     * 写法如下.antMatchers("/admin/**").hasIpAddress("210.210.210.210")
     * 7.更多的权限控制方式参看源码:
     */

代码如下:

package com.cyj.dream.auth.config;

import com.cyj.dream.auth.handler.*;
import com.cyj.dream.auth.persistence.service.impl.CustomUserServiceImpl;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @Description: WebSecurity配置
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.auth.config
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-30
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserServiceImpl userDetailsService;

    /**
     * 自定义登录成功处理器
     */
    @Autowired
    private MyAuthenticationSuccessHandler userLoginSuccessHandler;

    /**
     * 自定义登录失败处理器
     */
    @Autowired
    private MyAuthenticationFailureHandler userLoginFailureHandler;

    /**
     * 自定义注销成功处理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;

    /**
     * 自定义暂无权限处理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;

    /**
     * 自定义未登录的处理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserServiceImpl();
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    /**
     * 认证管理--需要配置这个支持password模式
     * <p>
     * support password grant type
     *
     * @return 认证管理对象
     * @throws Exception 认证异常信息
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 安全请求配置,这里配置的是Security的部分,请求全部通过,安全拦截在资源服务器配置
     * <p>
     * http安全配置
     *
     * @param http http安全对象
     * @throws Exception http安全异常信息
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置允许的请求以及跨域问题
        http.authorizeRequests()
                .antMatchers("/webjars/**", "/js/**", "/css/**", "/images/*", "/fonts/**", "/**/*.png", "/**/*.jpg",
                        "/static/**")
                // .anyRequest()
                .permitAll()
                // 所有请求都可以访问,本地开发打开下面一行的注释
                //.antMatchers("/**").permitAll()
                //不进行权限验证的请求或资源(从配置文件中读取),本地开发可以,将本行注释掉
                .antMatchers("/login", "/oauth/**")
                .permitAll()
                .antMatchers("/**")
                .fullyAuthenticated()
                .and()
                //配置未登录自定义处理类
                .httpBasic()
                .authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                .csrf()
                .disable()
                // 配置登录地址
                .formLogin()
                // 本地
                //.loginPage("/login/userLogin")
                //.loginProcessingUrl("/login/userLogin")
                .loginPage("/login")
                //配置登录成功自定义处理类
                .successHandler(userLoginSuccessHandler)
                //配置登录失败自定义处理类
                .failureHandler(userLoginFailureHandler)
                .permitAll()
                .and()
                //配置登出地址
                .logout()
                // /userInfo/loginOutByToken
                .logoutUrl("/login/loginOut")
                //配置用户登出自定义处理类
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                //配置没有权限自定义处理类
                .exceptionHandling()
                .accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable();
                /*.cors().and()
                .addFilterAt(ignoreLogoutFilter, LogoutFilter.class);*/
        // 为了可以使用 iframe 内嵌页面加载
        http.headers().frameOptions().sameOrigin();
        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用缓存
        http.headers().cacheControl();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 设置忽略资源
        web.ignoring().antMatchers(
                "/error",
                "/v2/api-docs/**",
                "/favicon.ico",
                "/css/**",
                "/js/**",
                "/images/*",
                "/fonts/**",
                "/**/*.png",
                "/**/*.jpg")
                // 不拦截 swagger2 所进行的配置
                .antMatchers("/templates/**")
                .antMatchers("/static/**")
                .antMatchers("/webjars/**")
                .antMatchers("/swagger-ui.html/**")
                .antMatchers("/v2/**")
                .antMatchers("/doc.html")
                .antMatchers("/api-docs-ext/**")
                .antMatchers("/swagger-resources/**");
        ;
    }

}

5. 创建一个 controller 进行访问测试


package com.cyj.dream.auth.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cyj.dream.auth.entity.SysUser;
import com.cyj.dream.auth.persistence.service.ITbSysUserService;
import com.cyj.dream.core.aspect.annotation.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

/**
 * @Description: 用户控制器
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.auth.controller
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-30 10:40
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Slf4j
@ResponseResult
@RestController
@RequestMapping(value = "/authUser", name = "用户控制器")
@Api(value = "authUser", tags = "用户控制器")
public class UserController {

    @Autowired
    private ITbSysUserService iTbSysUserService;

    @ApiOperation("通过名称获取用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userName", value = "用户名称", dataType = "String", required = true)
    })
    @RequestMapping(value = "getByName", method = RequestMethod.GET, name = "通过名称获取用户信息")
    public SysUser getByName(@RequestParam(value = "userName") String userName){
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getUsername, userName);
        return iTbSysUserService.getOne(wrapper);
    }

    @ApiOperation("获取授权的用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "principal", value = "当前用户", dataType = "Principal", required = true)
    })
    @RequestMapping(value = "current", method = RequestMethod.GET, name = "获取授权的用户信息")
    public Principal user(Principal principal){
        // 授权信息
        return principal;
    }

}

token 的请求

image-20200910202739906

不带 token 的请求

image-20200910202804042

token,但是没有ROLE_ADMIN权限

image-20200910203005094

6. 最后关于Fegin 调用服务 Token 丢失的问题


在微服务中我们经常会使用RestTemplate或Fegin来进行服务之间的调用,在这里就会出现一个问题,我们去调用别的服务的时候就会出现token丢失的情况,导致我们没有权限去访问。

所以我们需要加上一些拦截器将我们的 token 带走,针对fegin的配置代码如下:

public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();    
    // 设置请求头
    Enumeration<String> headerNames = request.getHeaderNames();
    if (headerNames != null) {
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            String value = request.getHeader(name);
            requestTemplate.header(name, value);
        }
    }

    // 设置请求体,这里主要是为了传递 access_token
    Enumeration<String> parameterNames = request.getParameterNames();
    StringBuilder body = new StringBuilder();
    if (parameterNames != null) {
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            String value = request.getParameter(name);

            // 将 Token 加入请求头
            if ("access_token".equals(name)) {
                requestTemplate.header("authorization", "Bearer " + value);
            }

            // 其它参数加入请求体
            else {
                body.append(name).append("=").append(value).append("&");
            }
        }
    }

    // 设置请求体
    if (body.length() > 0) {
        // 去掉最后一位 & 符号
        body.deleteCharAt(body.length() - 1);
        requestTemplate.body(body.toString());
    }
}
}

然后将这个拦截器加入到我们 fegin 请求拦截器中:

@Configuration
public class FeignRequestConfiguration {
	@Bean
	public RequestInterceptor requestInterceptor() {
    	return new FeignRequestInterceptor();
	}
}

7. Spring SecurityShiro


相同点:

1:认证功能

2:授权功能

3:加密功能

4:会话管理

5:缓存支持

6:rememberMe功能.......

不同点:

优点:

1:Spring Security基于Spring开发,项目中如果使用Spring作为基础,配合Spring Security做权限更加方便,而Shiro需要和Spring进行整合开发

2:Spring Security功能比Shiro更加丰富些,例如安全防护

3:Spring Security社区资源比Shiro丰富

缺点:

1:Shiro的配置和使用比较简单,Spring Security上手复杂

2:Shiro依赖性低,不需要任何框架和容器,可以独立运行,而Spring Security依赖于Spring容器

8. 数据库表

-- used in tests that use HSQL
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication LONGVARBINARY,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication LONGVARBINARY
);

create table oauth_code (
  code VARCHAR(256), authentication LONGVARBINARY
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);

-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

最后感谢大家耐心观看完毕,具体请求和一些结果返回可以参见上一章内容,留个点赞收藏是您对我最大的鼓励!


🎉总结:

  • 更多参考精彩博文请看这里:《陈永佳的博客》
  • 整合 auth 这里有一些类我没有放置,大家可以先阅读文章,后续我会把代码传到 git 上方便大家参考学习~
  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!