Spring Security+Oauth2+JWT实现用户登录逻辑,以及使用login接口登录成功返回token获取

目录

pom引入

配置Spring Security

1.实现UserDetailsService接口

2.登录成功处理

3.登录失败处理

4.登出处理

5.没有权限处理设置

6.匿名用户访问处理

7.指定加密方式

8.WebSecurityConfig配置

oauth2处理

配置授权服务器

配置资源服务器

jwt配置

jwt转换器

jwt扩展存储

本文整合了前两篇文章,再结合Oauth2实现了单点登录的基本处理。

之前的文章:

SpringBoot整合Spring Security实现前后端分离登录权限处理_zmgst的博客-CSDN博客

SpringBoot整合Spring Security+JWT实现前后端分离登录权限处理_zmgst的博客-CSDN博客

pom引入

 <properties>
        <java.version>11</java.version>
        <spring.security.version>5.1.6.RELEASE</spring.security.version>
        <fastjson.version>1.2.46</fastjson.version>
    </properties>
    <dependencies>
<!--       spring web  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--      spring security 依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
<!--        oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>
<!--        oauth2 整合jwt -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.9.RELEASE</version>
        </dependency>
<!--        jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!--    lombok 插件    -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--        redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
<!--        验证码 -->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>
        <!-- 解决jdk1.8之后删除jar报错 -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!--JSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
<!--    编码工具包  -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
<!-- ieda使用@ConfigurationProperties注解报错处理 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置Spring Security

关于SpringSecruity的基本知识,可以参考这位博主的专栏博客

https://blog.csdn.net/qq_32867467/category_9047805.html?spm=1001.2014.3001.5482

一些统一返回封装,数据库表创建处理我就不再写了,上一篇文章里去粘贴吧,这里只做主要流程的梳理

SpringBoot整合Spring Security实现前后端分离登录权限处理_zmgst的博客-CSDN博客

1.实现UserDetailsService接口

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysPermissionService sysPermissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(StringUtils.isEmpty(username)){
            throw new UsernameNotFoundException("用户名不能为空");
        }
        /**
         * 通过用户名称获取用户信息
         */
        SysUser user=sysUserService.getUserDetails(username);
        if(user==null){
            throw new UsernameNotFoundException("账号不存在,请联系管理员!");
        }
        if(1!=user.getState()){
            throw new RuntimeException("账户已被锁定,请联系管理员!");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        //获取该用户所拥有的权限
        List<SysPermission> sysPermissions = sysPermissionService.selectListPermissionByUser(user.getId());
        // 声明用户授权
        sysPermissions.forEach(sysPermission -> {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysPermission.getPermissionCode());
            grantedAuthorities.add(grantedAuthority);
        });
        SecuritySysUser ssu=new SecuritySysUser(user.getAccound(),user.getPassword(),grantedAuthorities);
        ssu.setSysuser(user);
        return ssu;
    }
}
SecuritySysUser为继承了UserDetails的实现类User,代码
@EqualsAndHashCode(callSuper = false)
public class SecuritySysUser extends User {
    /**
     * 用户信息
     */
    private SysUser sysuser;
    /**
     * 权限信息
     */
    private List<SysRole> roleList;
    /**
     * 构造方法
     * @param username
     * @param password
     * @param authorities  用户权限列表
     */
    public SecuritySysUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, true, true, true, true, authorities);
    }
}

两个请求的sql

<!-- 根据登录名获取用户信息 -->
<select id="getUserDetails" resultType="com.zm.test.entity.zm.SysUser">
    select * from sys_user where accound=#{username}
</select>
<select id="selectListPermissionByUser" resultType="com.zm.test.entity.zm.SysPermission">
    SELECT
    p.*
    FROM
    sys_user AS u
    LEFT JOIN sys_user_role AS ur ON u.id = ur.uid
    LEFT JOIN sys_role AS r ON r.id = ur.rid
    LEFT JOIN sys_role_permission AS rp ON r.id = rp.role_id
    LEFT JOIN sys_permission AS p ON p.id = rp.permission_id
    WHERE u.id = #{userId}
</select>

2.登录成功处理

@Component
public class CustomizeAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        //此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
        //进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展

        Map<String,String> results = new HashMap<>();
        //返回json数据
        JsonResult result = ResultTool.success(results);
        //处理编码方式,防止中文乱码的情况
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

3.登录失败处理

CaptchaException为自定义异常,不需要的去掉即可

@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //返回json数据
        JsonResult result = null;
        if (e instanceof CaptchaException) {
            //验证码错误
            result = ResultTool.fail(ResultCode.USER_CAPTCHA_ERROR);
        } else if (e instanceof AccountExpiredException) {
            //账号过期
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);
        } else if (e instanceof BadCredentialsException) {
            //密码错误
            result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);
        } else if (e instanceof CredentialsExpiredException) {
            //密码过期
            result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
        } else if (e instanceof DisabledException) {
            //账号不可用
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);
        } else if (e instanceof LockedException) {
            //账号锁定
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);
        } else if (e instanceof InternalAuthenticationServiceException) {
            //用户不存在
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
        }else{
            //其他错误
            result = ResultTool.fail(ResultCode.COMMON_FAIL);
        }
        //处理编码方式,防止中文乱码的情况
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

CaptchaException:

public class CaptchaException extends AuthenticationException {

    public CaptchaException(String msg) {
        super(msg);
    }
}

4.登出处理

@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(request, response, authentication);
        }
        JsonResult result = ResultTool.success();
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

5.没有权限处理设置

@Component
public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        JsonResult result = ResultTool.fail(ResultCode.NO_PERMISSION);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

6.匿名用户访问处理

@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

7.指定加密方式

在WebSecurityConfig配置文件添加
    /**
     * 指定加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

8.WebSecurityConfig配置

这里有些类可能还没有写到,在后面代码里,验证码过滤器的处理,想添加的可以参考之前博客:

SpringBoot整合Spring Security+JWT实现前后端分离登录权限处理_zmgst的博客-CSDN博客

/**
 * spring security 配置类
 * @Author: zm
 * @Description:
 * @Date: 2022/4/22 13:48
 */
@Configuration
@EnableWebSecurity  //开启Spring Security的功能
//prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled = true)
//@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 自定义用户登录操作
     */
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    /**
     * 匿名用户访问无权限资源时的异常
     */
    @Autowired
    private CustomizeAuthenticationEntryPoint authenticationEntryPoint;
    /**
     * 登陆失败执行方法
     */
    @Autowired
    private CustomizeAuthenticationFailureHandler authenticationFailureHandler;

    /**
     * 没有权限设置
     */
    @Autowired
    private CustomizeAccessDeniedHandler customizeAccessDeniedHandler;
    /**
     * 登出成功执行方法
     */
    @Autowired
    private CustomizeLogoutSuccessHandler logoutSuccessHandler;

    /**
     * 验证码过滤
     */
  //  @Autowired
 //   private CaptchaFilter captchaFilter;

    /**
     *登录成功执行方法
     * @return
     */
    @Bean
    public CustomizeAuthenticationSuccessHandler loginSuccessHandler() {
        return new CustomizeAuthenticationSuccessHandler();
    }

    /**
     * 指定加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * AuthenticationManager
     * <p>
     * 如果不声明,会导致授权服务器无AuthenticationManager,
     * 密码模式:而password方式无法获取token
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 用户名密码授权管理器
     *
     * @return DaoAuthenticationProvider
     */
    @Bean
    public UserNamePasswordAuthenticationProvider daoAuthenticationProvider() {
        return new UserNamePasswordAuthenticationProvider(userDetailsService, passwordEncoder());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable();
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/sysUser/addUser").permitAll() // 允许post请求/add-user,而无需认证
                .antMatchers("/sysUser/captcha").permitAll()//验证码放过
                .antMatchers("/login/**").permitAll()//验证码放过
                .antMatchers("/oauth/**").permitAll()//oauth2 请求路径放过
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated() //   有请求都需要验证

                //登入
                .and().formLogin().
                permitAll()//允许所有用户
                .successHandler(loginSuccessHandler()).//登录成功处理逻辑
                failureHandler(authenticationFailureHandler).//登录失败处理逻辑

                //登出
                and().logout().
                permitAll()//允许所有用户
                .logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑

                //异常处理(权限拒绝、登录失效等)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
                .accessDeniedHandler(customizeAccessDeniedHandler)

//                 无状态session,不进行存储 禁用session
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;
        http.addFilterBefore(securityInterceptor,FilterSecurityInterceptor.class);
        //http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);

    }
}
UserNamePasswordAuthenticationProvider:
public class UserNamePasswordAuthenticationProvider extends DaoAuthenticationProvider {


    /**
     * 构造方法
     *
     * @param userDetailsService 用户信息服务
     * @param passwordEncoder    密码工具
     */
    public UserNamePasswordAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this(userDetailsService, passwordEncoder, null);
    }
    /**
     * 构造方法
     *
     * @param userDetailsService         用户信息服务
     * @param passwordEncoder            密码工具
     * @param userDetailsPasswordService 修改密码服务
     */
    public UserNamePasswordAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, UserDetailsPasswordService userDetailsPasswordService) {
        super();
        setUserDetailsService(userDetailsService);
        setPasswordEncoder(passwordEncoder);
        setUserDetailsPasswordService(userDetailsPasswordService);
    }

    /**
     * 验证通过后,查询权限等信息
     *
     * @param principal      principal
     * @param authentication authentication
     * @param user           user
     * @return org.springframework.security.core.Authentication
     */
    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        Set<GrantedAuthority> authorities;
        SecuritySysUser securityUser = (SecuritySysUser) user;
        return super.createSuccessAuthentication(principal, authentication, securityUser);
    }

}

oauth2处理

Oauth2有4种授权类型,这里只展示一种常用的密码模式的使用

配置授权服务器

@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    /**
     * 自定义token处理
     */
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;
    /**
     * 配置认证客户端
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 配置client_id
                .withClient("client")
                // 配置client_secret
                .secret(passwordEncoder.encode("123123"))
                //配置访问token的有效期
                .accessTokenValiditySeconds(36000)
                //配置刷新token的有效期
                .refreshTokenValiditySeconds(864000)
                //配置redirect_uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                //配置申请的权限范围
                .scopes("all")
                /**
                 * 配置grant_type,表示授权类型
                 *
                 *  authorization_code:授权码模式
                 *  implicit:简化模式
                 *  password:密码模式
                 *  client_credentials: 客户端模式
                 *  refresh_token: 更新令牌
                 */
                .authorizedGrantTypes("password", "refresh_token");
    }

    /**
     * 自定义授权服务配置
     * 使用密码模式需要配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //增加转换链路,以增加自定义属性
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));

        endpoints.authenticationManager(authenticationManager)//使用密码模式需要配置
        .userDetailsService(userDetailsService) //刷新令牌授权包含对用户信息的检查
        .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)//支持GET,POST请求
        .reuseRefreshTokens(false)//refresh_token是否重复使用
        .tokenStore(tokenStore) // 配置存储令牌策略
//        .accessTokenConverter(jwtAccessTokenConverter) // token转化器,我们转为了JWT
        .tokenEnhancer(enhancerChain) ///配置自定义tokenEnhancer
        ; //支持GET,POST请求;
    }

    /**
     * 自定义授权令牌端点的安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

}

配置资源服务器

/**
 * @Author: zm
 * @Description: 配置资源服务器
 * @Date: 2022/4/26 13:26
 */
@Configuration
@EnableResourceServer
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .formLogin().and()
                .requestMatcher(requestMatcher())
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
    }

}

jwt配置

jwt转换器

/**
 * @Author: zm
 * @Description: jwt 配置文件
 * @Date: 2022/4/27 11:15
 */
@Configuration
public class JwtTokenStoreConfig {

    @Autowired
    private CustomProperties customProperties;

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        // 配置JWT使用的秘钥
        accessTokenConverter.setSigningKey(customProperties.getPrivateKey());
        return accessTokenConverter;
    }

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
}

jwt扩展存储

/**
 * @Author: zm
 * @Description: jwt token扩展存储
 * @Date: 2022/4/27 11:40
 */
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        String name=authentication.getName();
        SecuritySysUser userInfo = (SecuritySysUser) authentication.getPrincipal();

        Map<String, Object> info = new HashMap<>();
        info.put("enhance", "enhance info");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}
CustomProperties:
/**
 * @Author: zm
 * @Description: 自定义变量
 * @Date: 2022/4/27 11:11
 */
@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "zm.jwt")
public class CustomProperties {

    private long expire;
    private String secret;
    private String header;
    private String privateKey;
}
#application.yml配置文件
zm:
  jwt:
    header: Authorization
    expire: 604800 # 7天,s为单位
    secret: abcdefghabcdefghabcdefghabcdefgh
    privateKey: 123123

到此处我们的所有配置已经完成了,然后我们再修改之前的登录成功处理器CustomizeAuthenticationSuccessHandler的onAuthenticationSuccess方法,如下:

    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;


public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //更新用户表上次登录时间、更新人、更新时间等字段
        User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        SysUser sysUser = sysUserService.getUserDetails(userDetails.getUsername());
//        sysUser.setLastLoginTime(new Date());
        sysUser.setUpdateDate(LocalDateTime.now());
        sysUser.setUpdateBy(sysUser.getAccound());
        sysUserService.update(sysUser);

        //获取生成得token
        String header=httpServletRequest.getHeader(Constant.AUTHORIZATION);
        if(header==null  || !header.startsWith("Basic ")){
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }
        String[] tokens = this.extractAndDecodeHeader(header, httpServletRequest);
        String clientId = tokens[0];
        String clientSecret = tokens[1];

        // 2. 通过 ClientDetailsService 获取 ClientDetails
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
//        String secret=clientDetails.getClientSecret();
//        clientSecret=passwordEncoder.encode(clientSecret);
        TokenRequest tokenRequest = null;
        // 3. 校验 ClientId和 ClientSecret的正确性
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在");
        }
//        else if (passwordEncoder.matches(clientSecret,clientDetails.getClientSecret())) {
//            throw new UnapprovedClientAuthenticationException("clientSecret不正确");
//        }
        else {
            // 4. 通过 TokenRequest构造器生成 TokenRequest
            tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
        }
        // 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        // 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication
        OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        // 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken
        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication);

        //此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
        //进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展

        Map<String,String> results = new HashMap<>();
        results.put(Constant.AUTHORIZATION,new ObjectMapper().writeValueAsString(token));

        //返回json数据
        JsonResult result = ResultTool.success(results);
        //处理编码方式,防止中文乱码的情况
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(token));
    }


    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) {
        byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);

        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, StandardCharsets.UTF_8);
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
Constant里的数据:
/**
 * 权限header
 */
public static final String AUTHORIZATION = "Authorization";
/**
 * token开头
 */
public static final String BEARER_TYPE = "Bearer";
/**
 * token开头
 */
public static final String ACCESS_TOKEN = "access_token";

参考博文:

微服务安全Spring Security OAuth2实战_沮丧的南瓜的博客-CSDN博客_微服务springsecurity

Spring Security OAuth2自定义Token获取方式 | MrBird

Spring Security Oauth2 JWT、第三方登录、单点登录讲解,并使用Oauth2.0结合微服务进行单点登录_YxinMiracle的博客-CSDN博客_oauth单点登录第三方接入
Spring Security + OAuth2.0 + JWT 实现单点登录_资深糖分大叔的博客-CSDN博客
 

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
这里提供一个基于Spring Boot的Cloud+Security+JWT+OAuth2整合示例: 1. 首先添加依赖: ``` <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.1.0.RELEASE</version> </dependency> ``` 2. 在application.yml配置文件中添加以下配置: ``` spring: security: oauth2: client: registration: client-id: client-id: client-id client-secret: client-secret scope: read,write provider: custom-provider redirect-uri: http://localhost:8080/login/oauth2/code/custom-provider provider: custom-provider: token-uri: http://localhost:8080/oauth/token authorization-uri: http://localhost:8080/oauth/authorize user-info-uri: http://localhost:8080/user user-name-attribute: username jwt: secret: mySecretKey ``` 3. 创建一个SecurityConfig类来配置security: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> authorize .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .loginPage("/oauth2/authorization/custom-provider") ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withSecretKey(new SecretKeySpec("mySecretKey".getBytes(), SignatureAlgorithm.HS256.getJcaName())).build(); } } ``` 4. 创建一个RestController来测试: ``` @RestController public class TestController { @GetMapping("/test") public String test() { return "Hello World!"; } @GetMapping("/user") public Principal user(Principal principal) { return principal; } } ``` 5. 运行应用并尝试访问http://localhost:8080/test,应该会跳转到登录页面并要求输入自定义提供程序的凭据。成功登录后,应该会显示“Hello World!”的消息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值