spring security 项目级 个人总结

前言:

        全文仅讲解spring security框架的密码模式,通过密码登录获得accessToken和refresh_token的登录方式可以单独使用也可以作为微服务的认证服务,涉及spring boot、spring cloud等框架,应用有redis、oauth2协议、微信登录等。

大纲

        一、建表

        二、配置文件

                2.1 继承 WebSecurityConfigurerAdapter 

                2.2 资源服务器实现类

                2.3 授权服务器配置类

                2.4 redisConfig

                2.5  DefaultAuthenticationKeyGenerator实现类

        三、自定义登录逻辑

                3.1 UserDetailsService自定义登录逻辑

                3.2.UserDetails登录对象

                3.3 登出

                3.4 微信登录

        四、权限

        五、源码及文档

依赖:

       仅仅只说 spring security的依赖,security的依赖两个,一个redis依赖(一般要加,不知道不加可不可以),版本自己根据需要定义,正常项目开发需要spring-cloud-alibaba、spring-cloud、spring-boot的依赖,依赖会和nacos等...依赖冲突,可以使用maven Helper解决依赖冲突。idea-setting-Plugins-installed然后搜索maven Helper即可,如果需要图片可以百度:idea下载maven Helper查询搜索即可。  

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

一、建表

建表的步骤,可以由配置文件代替

创建oauth_client_details

client_id:客户端ID;

client_secret:客户端密钥,在表中需要 BCryptPasswordEncoder() 加密后的字段;

scope:作用域,客户端作用域,不同客户端权限不同;

authorized_grant_types:授权类型,填 authorization_code,password,refresh_token。code码认证模式、密码模式、支持refresh_token;

access_token_validity:token的有效期

refresh_token_validity:refresh_token的有效期

...其他可以不填

参数举例:

{

    "username":"1500117839@qq.com", 

    "password":"admin",

    "clientId":"system",

     "clientSecret":"system",

     "scope":"all",

     "grantType":"password"

}

// grantType : 表示认证模式是密码模式


sql如下:

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

二、配置文件:

配置WebSecurityConfigurerAdapter实现类、授权服务器配置类、资源服务器实现类

2.1 继承 WebSecurityConfigurerAdapter 

源码说认证用户身份,是要继承WebSecurityConfigurerAdapter的,同时实现configure的方法。 

passwordEncoder() 里面定义了密码编码方式(BCryptPasswordEncoder的加密算法),passwordEncoder() 是通过动态盐的方式加密,相同密码每一次加密的结果不一样,但是可以校验,encode()加密方法、match()校验方法。注意: 没有解密的方法,不能解密!!!

关闭跨域限制:http.csrf().disable()

放行的接口及静态资源:http.authorizeRequests().antMatchers().permitAll()

其他的接口全部认证:.anyRequest().authenticated()

放行表格登录.formLogin().permitAll()(可以不写)

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    /**
     * 授权服务管理
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/oauth/login" ,"/oauth/weChat/qr"
                        ,"/register/we_chat**","/register/**"
                      , "/doc.html" ,"/swagger-ui.html/**","/webjars/**","/swagger-resources/**"
                        ,"/"
                        ,"//v2/api-docs","/csrf"
                        ,"/agent/*","user/**"
                ).permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();
    }
}

 2.2 资源服务器实现类

资源服务器,可以不写,不写的时候spring security的校验是通过WebSecurityConfigurerAdapter 的实现类实现校验。写了校验会在资源服务器中校验,校验规则如下:

@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/oauth/login"
                        ,"/oauth/weChat/qr","/register/we_chat**","/register/**"
                        ,"/**"
                        , "/doc.html","/swagger-ui.html/**","/webjars/**","/swagger-resources/**"
                        ,"/","//v2/api-docs","/csrf"

                ).permitAll()
                .anyRequest().authenticated()
        ;
    }

}

2.3 授权服务器配置类

继承 AuthorizationServerConfigurerAdapter 

authenticationManager:授权管理器,正常情况只用注入即可;

tokenStore(重点):令牌存储的地方,注入RedisConfig类中的redisTokenStore()方法,redisTokenStore()该方法将DefaultAuthenticationKeyGenerator的实现类MyAuthenticationKeyGenerator,set进redisTokenStore中,保证每个相同的用户每次刷新的token是不一样的,保证了每次登录后token的时间都是最新的,如果不定义则每次登录的用户的token在失效前时间是不会重新更新的。

dataSource:定义了数据源,简单点说就是你是mysql就定义mysql,oracle就定义oracle,这部分根据自己的来,百度上都有。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private LoginUserServiceImpl userService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    @Qualifier("redisTokenStore")
    private TokenStore tokenStore;
    @Resource
    private DataSource dataSource;


    //这个是定义授权的请求的路径的Bean
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                //自定义登录逻辑
                .userDetailsService(userService)
                //授权管理器
                .authenticationManager(authenticationManager)
                //令牌存储的地方
                .tokenStore(tokenStore)
        ;
    }
}

2.4 redisConfig

两部分:

        1.定义redisTokenStore()

        2.定义redisTemplate,使用redis和通过redis储存token需要的必要配置。

/**
 * 同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
 * 同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
 * 同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。
 * 这里使用第二种:参考https://www.cnblogs.com/cq-yangzhou/p/13207069.html
 * @Auther: liuYiZhao
 * @Date: 2021-08-06 - 18:02
 */
@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Bean
    public TokenStore redisTokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setAuthenticationKeyGenerator(new MyAuthenticationKeyGenerator());
        return redisTokenStore;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

//        // 使用Jackson2JsonRedisSerialize 替换默认序列化
//        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//
//        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.setDefaultSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setEnableDefaultSerializer(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

 2.5  DefaultAuthenticationKeyGenerator实现类

自定义DefaultAuthenticationKeyGenerator实现类,保证每个相同的用户每次刷新的token是不一样的,保证了每次登录后token的时间都是最新的,如果不定义则每次登录的用户的token在失效前时间是不会重新更新的。

/**
 * @Auther: liuYiZhao
 * @Date: 2021-08-18 - 11:18
 */
public class MyAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {

    private static final String CLIENT_ID = "client_id";

    private static final String SCOPE = "scope";

    private static final String USERNAME = "username";
    @Override
    public String extractKey(OAuth2Authentication authentication) {
        Map<String, String> values = new LinkedHashMap<String, String>();
        OAuth2Request authorizationRequest = authentication.getOAuth2Request();
        if (!authentication.isClientOnly()) {
            //在用户名后面添加时间戳,使每次的key都不一样
            values.put(USERNAME, authentication.getName()+System.currentTimeMillis());
        }
        values.put(CLIENT_ID, authorizationRequest.getClientId());
        if (authorizationRequest.getScope() != null) {
            values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
        }
        return generateKey(values);
    }
}

三、自定义登录逻辑

        3.1 UserDetailsService(重点):自定义登录逻辑,可以设置登录方式,如:微信登录、手机登录、邮箱登录等。

        3.2.UserDetails(重点):登录对象。

        3.3 登出。

3.1 UserDetailsService(重点):校验的方式只能通过loadUserByUsername()方法的参数username,其中可以根据username这个唯一标识,设置你期望的密码或其他的登录方式。要注意:返回的UserDetails是用户对象,可以继承使用自己的逻辑,也可以不继承使用spring security给你封装的User类。下例:我是返回了继承UserDetails的自定义LoginUser对象。

@Service
@Slf4j
public class LoginUserServiceImpl implements UserDetailsService, LoginUserService {


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        YucUser yucUser = null;
        String substring = username.substring(username.length() - 7);

        //微信登录
        if (substring.equals("-weChat")) {
            String[] split = username.split("-");
            return new LoginUser(split[0], passwordEncoder.encode(split[0]), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
        //账号密码登录,从用户关系表中找到用户登录凭证
        if (PatternUtil.isEmail(username)) {//邮箱登录
            String userCode = yucUserRelationsService.findUserCodeByOnly(ParamUtil.C_B_EMAIL, username);
            if (userCode == null || userCode.equals("")) {
                YucUser yucUserByRegType = yucUserService.findYucUserByRegType(ParamUtil.C_EMAIL, username);
                userCode = yucUserByRegType.getUserCode();
            }
            yucUser = yucUserService.findYucUserByOnly(YucUserService.KEY_USER_CODE, userCode);
        } else if (PatternUtil.isMobileNo(username)) {//手机登录
            String userCode = yucUserRelationsService.findUserCodeByOnly(ParamUtil.C_B_PHONE, username);
            if (userCode == null || userCode.equals("")) {
                YucUser yucUserByRegType = yucUserService.findYucUserByRegType(ParamUtil.C_PHONE, username);
                userCode = yucUserByRegType.getUserCode();
            }
            yucUser = yucUserService.findYucUserByOnly(YucUserService.KEY_USER_CODE, userCode);
        }

        return new LoginUser(username, yucUser.getEngyptPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }


}

4.2  UserDetails:

下例:LoginUser继承自定义YucUser用户类,可以方便自定义YucUser用户对象的属性,实现UserDetails是为了定义出上面,自定义登录逻辑的用户登录的类。

public class LoginUser extends YucUser implements UserDetails, Serializable {
 private String username;
    private String password;

    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 账号已过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
//        return getYn();
    }
}

4.3 登出

spring security框架封装ConsumerTokenServices接口,用于清除redis保存Token类型的方法。下例中:同时也清除保存在redis中的用户信息。

  @Autowired
  private ConsumerTokenServices consumerTokenServices;  


  public Boolean logout( ) {
        boolean flag = consumerTokenServices.revokeToken(accessToken);

        Boolean delete = redisOperateManager.deleteString("access_token:" + accessToken);
        return flag && delete;
    }

注意:

如果想清除非redis保存的令牌,spring security框架TokenStore接口中的removeAccessToken方法中,有相应的实现类(如下图),同时也可以自己继承后定义登录逻辑。

 

3.4 微信登录

四、权限

五、spring 官方文档

5.1  spring security 官方文档列出的过滤链,从上到下的排序。

 

 

未写完待定……

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唯月作伴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值