oauth2单点登录总结

OAuth 2.0授权框架支持第三方支持访问有限的HTTP服务,通过在资源所有者和HTTP服务之间进行一个批准交互来代表资源者去访问这些资源,或者通过允许第三方应用程序以自己的名义获取访问权限。

oauth2单点登录总结_第1张图片

 

1.认证授权服务配置

主要配置继承AuthorizationServerConfigurerAdapter的三个配置,其他的属于配置类中方法或参数的加强和修饰。

package com.panda.admin.login.authconfig;

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.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
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.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.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.Arrays;

/**
 * author: tcy
 * createDate: 2022/10/26
 * description:认证授权服务配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private SysUserDetailServiceImpl sysUserDetailService;

    /**
     * redis工厂
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 用来配置令牌的安全约束
     * 拦截控制
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.checkTokenAccess("permitAll()")                                 // check_token公开
                .tokenKeyAccess("permitAll()")                                   // token_key公开
                .allowFormAuthenticationForClients();                            // 表单认证(申请令牌)
    }

    /**
     * 用来配置客户端详情服务(ClientDetailsService)
     * 允许客户端自己申请ClientID
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /**
         * 数据库中添加客户端信息表,可以启用withClientDetails
         * 还需要自己建立ClientDetailsService的实现类ClientDetailsServiceImpl
         * ClientDetailsServiceImpl implements ClientDetailsService
         * 在实现类中将客户端相关数据配置好
         */
        //clients.withClientDetails(clientDetailsService);

        /**
         * 客户端配置唯一,直接在代码中配置也可以
         * 但是保密性不强,安全性不高
         */
        clients.inMemory()
               .withClient("client")                                               //注册客户端id
               .secret(passwordEncoder.encode("secret"))                      //注册客户端密码
               .accessTokenValiditySeconds(60)                                            //有效时间60秒,测试阶段容易看出效果
               .authorizedGrantTypes("authorization_code","password","refresh_token")     //三方登录、密码授权、刷新令牌
               .autoApprove(true)                                                         //登录后绕过批准询问(/oauth/confirm_access)
               .scopes("all")                                                             //允许授权范围
               .redirectUris("https://gitee.com/");                                       //回调网址,携带授权码
    }

    /**
     * 管理令牌:用来配置令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        //token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));

        endpoints.authenticationManager(authenticationManager)
                 .accessTokenConverter(jwtAccessTokenConverter())
                 .tokenStore(tokenStore())
                 .tokenEnhancer(tokenEnhancerChain)

                /** refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
                 *  1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
                 *  2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
                 */
                .reuseRefreshTokens(true)
                .userDetailsService(sysUserDetailService);
    }

    /**
     * 自定义的Token增强器,把更多信息放入Token中
     * 登录者的id、角色、手机号、其他绑定账号等
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new EnhanceTokenEnhancer();
    }

    /**
     * 配置JWT令牌
     */
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        //设置jwt的转换器
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        /**
         * 非对称加密:密钥库中获取密钥对(公钥+私钥)
         * 需要 *.jks文件
         */
        //KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jks/jwt.jks"), "123456".toCharArray());
        //KeyPair keyPair = keyStoreKeyFactory.getKeyPair("jwt","888888".toCharArray());             //设置获取秘钥的密码
        //converter.setKeyPair(keyPair);                                                             //设置秘钥对象

        /**
         * 对称加密
         */
        converter.setSigningKey("Tu");
        return converter;
    }

    /**
     * JWT令牌存储
     */
    @Bean
    public TokenStore tokenStore() {

        /**
         * springSecurity中TokenStore实现有4种,分别是:
         *    InMemoryTokenStore(保存本地内存)
         *    JdbcTokenStore(保存到数据库)
         *    JwkTokenStore(全部信息返回到客户端)
         *    RedisTokenStore(保存到redis)
         */

        //存放至redis库,注意pom和yml中相关配置
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("auth:token:access:");
        return redisTokenStore;

        //return new JwtTokenStore(jwtAccessTokenConverter());
    }


}

2.安全配置

主要为密码、身份认证、请求放行

package com.panda.admin.login.authconfig;

import com.panda.admin.login.auth.service.impl.SysUserDetailServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

/**
 * author: tcy
 * createDate: 2022/10/27
 * description:安全配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private SysUserDetailServiceImpl sysUserDetailService;


    /**
     * 密码编码器
     *
     * 委托方式,根据密码的前缀选择对应的encoder,
     * 例如:{bcypt}前缀->标识BCYPT算法加密;
     *     {noop}->标识不使用任何加密即明文的方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        //秘文编辑密码
        //return new BCryptPasswordEncoder();

        //编辑工厂,可用多种方式编辑
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 身份验证管理器
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {

        /**
         * 测试用户,单独提供
         * 只能用这个用户才能登录
         */
        //auth.inMemoryAuthentication()
        //    .withUser("panda")
        //    .password(passwordEncoder().encode("123456"))
        //    .roles("admin");

        /**
         * 这里从数据库查询用户,但是登录方式唯一
         */
        //auth.userDetailsService(sysUserDetailService);

        /**
         * 可以匹配多种登录方式
         * 账号密码、手机验证码、第三方授权等
         */
        auth.authenticationProvider(daoAuthenticationProvider());
      }

    /**
     * 账号密码 认证授权提供者
     */
    @Bean
      public DaoAuthenticationProvider daoAuthenticationProvider(){
          DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
          provider.setUserDetailsService(sysUserDetailService);
          provider.setPasswordEncoder(passwordEncoder());
          provider.setHideUserNotFoundExceptions(false);  //是否隐藏用户不存在异常

          return provider;
      }

    /**
     * 配置放行规则
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .antMatchers("/login/**").permitAll()
            .antMatchers("/webjars/**", "/doc.html", "/swagger-resources", "/v2/api-docs","swagger-ui.html").permitAll()
            .antMatchers("/actuator/**","/instances/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsService(){
        return super.userDetailsService();
    }
}

3.token增强

这部分可以直接放在AuthorizationServerConfig的token增强器中

package com.panda.admin.login.authconfig;

import com.panda.admin.login.constant.SecurityConstants;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

/**
 * author: tcy
 * createDate: 2022/10/26
 * description:Token增强器
 */
public class EnhanceTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
        if (userAuthentication != null) {
            Map additionalInfo = new HashMap<>();
            additionalInfo.put(SecurityConstants.DETAILS_USER_ID, 2629738803L);
            additionalInfo.put(SecurityConstants.DETAILS_USERNAME, "承天永明");
            additionalInfo.put(SecurityConstants.DETAILS_NICKNAME, "飒飒");
            additionalInfo.put(SecurityConstants.OPEN_ID, "123456789");
            additionalInfo.put(SecurityConstants.ROLE_ID, "1");
            additionalInfo.put(SecurityConstants.MOBILE, "19108217140");
            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
        }
        return oAuth2AccessToken;
    }
}

4.登录客户的信息

package com.panda.admin.login.auth.Details;

import com.panda.admin.login.SystemUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;

/**
 * author: tcy
 * createDate: 2022/11/9
 * description:系统管理用户认证信息
 */
@Data
public class SysUserDetails implements UserDetails, Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;
    private String nickName;

    /**
     * UserDetails默认字段
     */
    private String username;
    private String password;
    private Collection authorities;

    public SysUserDetails(SystemUser user){
        this.setId(user.getId());
        this.setUsername(user.getNickname());
        //数据表中,账号为account字段
        this.setUsername(user.getAccount());
        this.setPassword(user.getPassword());
    }

    @Override
    public Collection getAuthorities() {
        return this.authorities;
    }

    //获取密码
    @Override
    public String getPassword() {
        return this.password;
    }

    //获取用户名
    @Override
    public String getUsername() {
        return this.username;
    }

    //账号是否可用
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //账号是否锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //凭证是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //用户是否正常
    @Override
    public boolean isEnabled() {
        return true;
    }
}

5.系统用户信息查询

package com.panda.admin.login.auth.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.panda.admin.login.SystemUser;
import com.panda.admin.login.auth.Details.SysUserDetails;
import com.panda.admin.result.ServiceException;
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;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;

/**
 * author: tcy
 * createDate: 2022/11/9
 * description:系统用户信息
 */
@Service
public class SysUserDetailServiceImpl implements UserDetailsService {

    @Resource
    private SystemUserServiceImpl systemUserService;

    /**
     * 账号密码 认证
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUserDetails sysUserDetails = null;

        //查询用户存在
        SystemUser systemUser = this.systemUserService.lambdaQuery().eq(SystemUser::getAccount, username).one();

        if (ObjectUtil.isNotEmpty(systemUser)){
            sysUserDetails = new SysUserDetails(systemUser);
        }
        if (ObjectUtils.isEmpty(systemUser) || ObjectUtils.isEmpty(sysUserDetails)){
            throw new ServiceException("用户不存在");
        }
        return sysUserDetails;
    }
}

6.明文密码获取token

/token/oauth接口是oauth2配置自带,不用建立相关接口,只需要模块的本地接口对上

(1)在用户表中创建用户数据,密码为明文

(2)在系统用户信息SysUserDetailServiceImpl中添加以下内容,{noop}表示不加密

if (ObjectUtil.isNotEmpty(systemUser)){
    systemUser.setPassword("{noop}" + systemUser.getPassword());
    sysUserDetails = new SysUserDetails(systemUser);
}

(3)使用apifox,配置用户信息,配置客户端信息,然后发送请求

oauth2单点登录总结_第2张图片

 oauth2单点登录总结_第3张图片

得到的结果:

oauth2单点登录总结_第4张图片

 7.建立秘文密码用户数据

(1)建立秘文接口

package com.panda.admin.login.auth.controller;

import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * author: tcy
 * createDate: 2022/11/9
 * description:
 */
@Api(tags = "安全")
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("/info")
    public String info(){
        String secret = passwordEncoder.encode("tcy510823");
        return secret;
    }
}

(2)apifox中建立相关请求,将第6步获取的token放在请求头Authorization中,注意:bearer + token。

oauth2单点登录总结_第5张图片

 (3)复制秘文密码,在数据库中建立用户数据

 

8.秘文密码获取token

(1)去掉SysUserDetailServiceImpl中的不加密操作

systemUser.setPassword("{noop}" + systemUser.getPassword());

(2)更换用户信息发送请求

oauth2单点登录总结_第6张图片

 

9.校验token

(1)/oauth/check_token为oauth2自带接口

(2)可以加工,得到适合的反馈效果

@Api(tags = "oauth登录安全")
@RestController
@RequestMapping("/oauth")
public class SystemUserController {
    
    @Autowired
    TokenStore tokenStore;

    @ApiOperation(value = "校验token")
    @GetMapping("/check_token")
    public Result checkToken(@RequestParam("token") String token) {

        OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(token);
        if (ObjectUtils.isEmpty(oAuth2AccessToken) || oAuth2AccessToken.isExpired()){
            throw new ServiceException("token无法识别或已经过期");
        }

        OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(token);
        if (ObjectUtils.isEmpty(oAuth2Authentication)){
            throw new ServiceException("验证信息无效或已过期");
        }
        int expiresIn = oAuth2AccessToken.getExpiresIn();
        return Result.success("token校验通过,有效时间:" + expiresIn + "s");
    }



}

(3)复制上面得到的token,放入请求参数中

oauth2单点登录总结_第7张图片

 (4)结果

oauth2单点登录总结_第8张图片

oauth2单点登录总结_第9张图片

 

10.刷新token

(1)使用http://localhost:9001/oauth/token接口

(2)grant_type需要换成refresh_token

(3)refresh_token的值为之前请求,获取的refresh_token

oauth2单点登录总结_第10张图片

(4)结果,注意:"expires_in": 59 

oauth2单点登录总结_第11张图片

 

你可能感兴趣的:(总结,java,redis)