SpringSecurity Oauth2实战 - 04 自定义AuthProvider实现登录认证

news2024/11/29 2:33:05

文章目录

      • 1. 搭建资源服务器
        • 1. Token存储配置类 TokenStoreAutoConfiguration
        • 2. 资源服务器配置类 ResourceServerAutoConfiguration
        • 3. 在META-INF/spring.factories文件下添加配置类
      • 2. 搭建授权服务器
        • 1. 密码加密配置类 PasswordEncodeConfig
        • 2. RestTemplateConfig
        • 3. 授权服务器配置类 AuthorizationServerConfiguration
        • 4. 数据库表的数据准备
        • 5. 密码加密工具类 BcryptUtil
      • 3. 自定义AuthProvider实现认证登录
        • 1. 登录请求入口 AuthController
        • 2. 登录成功的响应类 AuthenticationInfo
        • 3. 登录认证 LoginServiceImpl
        • 4. 自定义认证管理器 CustomerAuthProvider
        • 5. 自定义UserDetailService实现类 CustomUserDetailService
        • 6. 认证成功后的用户对象 AuthUser
        • 7. 安全配置类中添加自定义认证管理器 WebSecurityConfig
        • 8. 启动项目测试登录功能

这是一个系列文章,具体项目结构可以参考这篇文章: SpringSecurity Oauth2实战 - 01 搭建授权服务器(密码模式)

1. 搭建资源服务器

1. Token存储配置类 TokenStoreAutoConfiguration

@Configuration
public class TokenStoreAutoConfiguration {

    @Autowired
    private RedisConnectionFactory connectionFactory;

    /**
     * 初始化 RedisTokenStore 用于将 token 存储至 Redis
     */
    @Bean
    public RedisTokenStore redisTokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
        // 设置key的层级前缀,方便查询
        redisTokenStore.setPrefix("TOKEN:");
        return redisTokenStore;
    }
}

2. 资源服务器配置类 ResourceServerAutoConfiguration

@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
    }
}

3. 在META-INF/spring.factories文件下添加配置类

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hh.config.ResourceServerAutoConfiguration,\
com.hh.config.TokenStoreAutoConfiguration

2. 搭建授权服务器

1. 密码加密配置类 PasswordEncodeConfig

@Configuration
public class PasswordEncodeConfig {

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

2. RestTemplateConfig

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

3. 授权服务器配置类 AuthorizationServerConfiguration

/**
 * 配置授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("refreshTokenUserDetailService")
    private UserDetailsService refreshTokenUserDetailService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private TokenStore tokenStore;

    /**
     * 用来配置授权服务器可以为哪些客户端授权,使用哪种授权模式
     *
     * 密码模式:在密码模式中,用户向客户端提供用户名和密码,客户端通过用户名和密码到认证服务器获取令牌
     * (A)用户访问客户端,提供URI连接包含用户名和密码信息给授权服务器
     * (B)授权服务器对客户端进行身份验证
     * (C)授权通过,返回access_token给客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 客户端id
                .withClient("client_id")
                // 客户端秘钥
                .secret(passwordEncoder.encode("client_secret"))
                // 授权模式
                .authorizedGrantTypes("password", "refresh_token", "client_credentials")
                // access_token的过期时长
                .accessTokenValiditySeconds(43200)
                // refresh_token的过期时长
                .refreshTokenValiditySeconds(86400)
                // 允许授权的范围
                .scopes("all");
    }

    /**
     * 刷新令牌必须配置userDetailsService,用来刷新令牌时的认证
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 配置token的存储方式
        endpoints.tokenStore(tokenStore);
        // 配置认证管理器
        endpoints.authenticationManager(authenticationManager);
        // 配置认证数据源,刷新令牌时的认证
        endpoints.userDetailsService(refreshTokenUserDetailService);
    }

    /**
     * 开启token检查接口
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
        security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
    }
}

注意: 刷新令牌必须配置 UserDetailsService,用来刷新令牌时的认证,这里配置了一个RefreshTokenUserDetailServiceImpl 实现 UserDetailsService 接口,默认使用的账号密码验证方式会自动加载DaoAuthenticationProvider类,该类会自动扫描一个UserDetailService实现类并注入,如果存在多个UserDetailService实现类会注入失败。

@Slf4j
@Service("refreshTokenUserDetailService")
public class RefreshTokenUserDetailServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    }
}

4. 数据库表的数据准备

因为项目使用的flyway组件,因此项目启动时就会执行sql脚本创建表并添加表中数据:

-- 用户表
CREATE TABLE `user`(
    `id`         varchar(64)  NOT NULL COMMENT '用户id',
    `username`   varchar(50)  NOT NULL COMMENT '账户名称',
    `password`   varchar(200) NOT NULL COMMENT '用户密码密文',
    `phone`      varchar(20) DEFAULT NULL COMMENT '手机号码',
    `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

-- 角色表
CREATE TABLE `role`(
    `id`          varchar(64) NOT NULL COMMENT '主键',
    `name`        varchar(255) DEFAULT NULL COMMENT '角色名称',
    `description` varchar(300) DEFAULT NULL,
    `createTime`  datetime     DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

-- 权限表
CREATE TABLE `policy`(
    `id`         varchar(32) NOT NULL,
    `name`       varchar(64) NOT NULL COMMENT '权限策略名',
    `createTime` timestamp   NOT NULL DEFAULT current_timestamp() COMMENT '创建时间',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMMENT ='权限策略表';

-- 用户角色表
CREATE TABLE `user_role`(
    `id`         varchar(64) NOT NULL COMMENT '主键',
    `userId`     varchar(64) DEFAULT NULL COMMENT '用户id',
    `roleId`     varchar(64) DEFAULT NULL COMMENT '角色id',
    `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

-- 角色权限表
CREATE TABLE `role_policy`(
    `id`         varchar(64) NOT NULL COMMENT '主键',
    `roleId`     varchar(64) DEFAULT NULL COMMENT '角色id',
    `policyId`   varchar(64) DEFAULT NULL COMMENT '菜单权限id',
    `createTime` datetime    DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 添加两个用户
INSERT INTO `authority`.`user` (`id`, `username`, `password`, `phone`, `createTime`) VALUES ('c24e51170fc64702ab38778e6562d8ac', 'zhangsan', '$2a$12$ECL54x35.kTMjNuefp28nOJu3bnTY7TPyVLGEMfapem10m1u0hMze', '18751779090', '2022-11-04 06:11:35');
INSERT INTO `authority`.`user` (`id`, `username`, `password`, `phone`, `createTime`) VALUES ('d20ff7f13c61474687b7cb6148e2823b', 'root', '$2a$12$f2csfxV4IE6/lLD.4ZWJCOGOLNtjYhYfPMn20s1bMOkKN/RtaBOTe', '18751779090', '2022-11-04 15:49:05');

-- 添加两个角色
INSERT INTO `authority`.`role` (`id`, `name`, `description`, `createTime`) VALUES ('3e65cd3532d94feb9bdaade9c09b8f08', '超级管理员', '超级管理员', '2022-11-04 06:18:41');
INSERT INTO `authority`.`role` (`id`, `name`, `description`, `createTime`) VALUES ('bca2b44e287642fb9eac63d2f0f08b4b', '平台管理员', '平台管理员', '2022-11-04 06:12:24');

-- 添加5个权限
INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('5eca3c4d9c224580855edb876beaa0ce', 'userEdit', '2022-11-04 06:16:29');
INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('618717f96b9a4f0380571ab203a5ac75', 'superAdmin', '2022-11-04 06:16:57');
INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('cab872e977824f65849b4ae1fc763d84', 'knowledgeQuery', '2022-11-04 06:16:09');
INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('fb693bf8ec13424e8ee370c08a597fcf', 'userQuery', '2022-11-04 06:16:40');
INSERT INTO `authority`.`policy` (`id`, `name`, `createTime`) VALUES ('fd60b1eaf222482496bb9ec5ec3f86dc', 'knowledgeEdit', '2022-11-04 06:16:00');

-- 给zhangsan用户关联2个角色
INSERT INTO `authority`.`user_role` (`id`, `userId`, `roleId`, `createTime`) VALUES ('cdcc6217725b49efa733f5e50b09ab7a', 'c24e51170fc64702ab38778e6562d8ac', '3e65cd3532d94feb9bdaade9c09b8f08', '2022-11-04 06:20:37');

-- 给超级管理员角色添加5个权限
INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('adb5aead86f444ec8aef9f8e60fdc649', '3e65cd3532d94feb9bdaade9c09b8f08', '618717f96b9a4f0380571ab203a5ac75', '2022-11-04 06:24:27');
INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('c6028a63797e44e99e83d9d1cb87e11b', '3e65cd3532d94feb9bdaade9c09b8f08', '5eca3c4d9c224580855edb876beaa0ce', '2022-11-04 06:24:12');
INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('c7c8ac1db9b04cf387bb886a7d71834a', '3e65cd3532d94feb9bdaade9c09b8f08', 'fb693bf8ec13424e8ee370c08a597fcf', '2022-11-04 06:24:48');
INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('dda442d423de4386bfb43a7126a03c12', '3e65cd3532d94feb9bdaade9c09b8f08', 'fd60b1eaf222482496bb9ec5ec3f86dc', '2022-11-04 06:25:02');
INSERT INTO `authority`.`role_policy` (`id`, `roleId`, `policyId`, `createTime`) VALUES ('ff95697aa0c54d83af00e8e36816b5fa', '3e65cd3532d94feb9bdaade9c09b8f08', 'cab872e977824f65849b4ae1fc763d84', '2022-11-04 06:24:40');

5. 密码加密工具类 BcryptUtil

/**
 * bcrypt算法工具
 * bcrypt加密算法属于单向hash加密, salt与密文按照约定拼接, 不用单独管理salt, 每次生成不同密文, 即使明文相同
 */
@Slf4j
public class BcryptUtil {
    /**
     * bcrypt加密算法的long_round参数配置, 安全基线明确要求>=12
     */
    private static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(12);

    /**
     * 对密码进行加密
     *
     * @param rawPwd 原始密码
     * @return 加密后的密码
     */
    public static String bEncryptEncode(String rawPwd) {
        return bCryptPasswordEncoder.encode(rawPwd);
    }

    /**
     * 进行加盐的比对
     * 
     * @param rawPwd 原有的密码
     * @param dbPwd  加密后的密码
     * @return boolean 是否相同
     */
    public static boolean bEncryptMatch(String rawPwd, String dbPwd) {
        return bCryptPasswordEncoder.matches(rawPwd, dbPwd);
    }
}

3. 自定义AuthProvider实现认证登录

1. 登录请求入口 AuthController

@Slf4j
@RestController
@RequestMapping("/api/v1")
public class AuthController {

    @Autowired
    private LoginService loginService;

    @PostMapping("/login")
    public ApiResponse<AuthenticationInfo> authority( @Validated @RequestBody LoginQo loginQo){
        AuthenticationInfo authenticationInfo = loginService.checkAndAuth( loginQo);
        return new ApiResponse<>(0,"success",authenticationInfo);
    }
}

注意:/api/v1/login 请求资源时受保护的资源,因此需要在资源服务器中放行该请求

@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 放行的请求
            .antMatchers("/api/v1/login").permitAll()
            // 其他请求必须认证才能访问
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }
}

2. 登录成功的响应类 AuthenticationInfo

@Data
public class AuthenticationInfo {

    /*令牌对象*/
    private AuthToken authToken;

    /*用户详情*/
    private User user;
}

① 登录成功返回的用户信息 User

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 160958617672077663L;
    /**
    * 用户id
    */
    private String id;
    /**
    * 账户名称
    */
    private String username;
    /**
    * 用户密码密文
    */
    private String password;
    /**
    * 手机号码
    */
    private String phone;
    /**
    * 创建时间
    */
    private Date createTime;
}

② 认证成功返回的 AuthToken

/**
 * 认证返回的AccessToken
 */
@Data
public class AuthToken {

    /*令牌*/
    private String accessToken;

    /*令牌类型*/
    private String tokenType;

    /*刷新使用的令牌*/
    private String refreshToken;

    /*过期时间长,单位:秒*/
    private Integer expiresIn;

   /*令牌的作用范围*/
    private String scope;
}

③ 认证的主体

/**
 * 用于jwt认证的主体
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthJwtPrincipal implements Serializable {
    /*用户ID*/
    private String userId;
}

3. 登录认证 LoginServiceImpl

@Service
@Slf4j
public class LoginServiceImpl implements LoginService {

    @Autowired
    private UserService userService;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 登录认证
     * @param loginQo 登录请求体
     * @return AuthenticationInfo
     */
    @Override
    public AuthenticationInfo checkAndAuth(LoginQo loginQo) {
        // 密码认证:数据库中的密码是经过bcrypt加密算法存储的
        User user = userService.queryByName(loginQo.getName());
        String dbEncryptPwd = user.getPassword();
        boolean isPassed = BcryptUtil.bEncryptMatch(loginQo.getPassword(), dbEncryptPwd);
        // 登陆失败
        if (!isPassed) {
            log.info("the user: {} login failed, account or password is wrong", user.getId());
            throw new RuntimeException("用户账号或者密码错误");
        }
        // 登录成功,获取用户认证信息
        AuthJwtPrincipal authJwtPrincipal = AuthJwtPrincipal.builder().userId(user.getId()).build();
        AuthenticationInfo authenticationInfo =  obtainAuthenticationInfo(authJwtPrincipal,user);
        String accessToken = authenticationInfo.getAuthToken().getAccessToken();
        if(StringUtils.isBlank(accessToken)){
            throw new RuntimeException("用户账号或者密码错误");
        }
        return authenticationInfo;
    }

    /**
     * 获取认证信息
     * @param authJwtPrincipal 认证主体
     * @param user 用户信息
     * @return AuthenticationInfo
     */
    private AuthenticationInfo obtainAuthenticationInfo(AuthJwtPrincipal authJwtPrincipal,User user) {
        AuthenticationInfo authenticationInfo = new AuthenticationInfo();
        // SpringSecurity Oauth2获取access_token
        AuthToken authToken = getAccessToken(authJwtPrincipal);
        authenticationInfo.setAuthToken(authToken);
        authenticationInfo.setUser(user);
        return authenticationInfo;
    }

    /**
     *  通过登陆信息获取相应的令牌对象
     * @param authJwtPrincipal 认证主体
     * @return AuthToken
     */
    public AuthToken getAccessToken(AuthJwtPrincipal authJwtPrincipal) {
        String loginJsonString = JSON.toJSONString(authJwtPrincipal);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", loginJsonString);
        // 因为使用的是自定义认证方式 CustomerAuthProvider,密码用不到,所以设为一个空值即可
        map.add("password", StringUtils.EMPTY);
        map.add("client_id", "client_id");
        map.add("client_secret","client_secret");
        map.add("grant_type", "password");
        map.add("scope", "all");
        // 这里会进行Oauth2.0的请求 在CustomerAuthProvider进行认证处理
        Map response = restTemplate.postForObject("http://127.0.0.1:8081/oauth/token", map, Map.class);
        if (MapUtil.isEmpty(response)) {
            return null;
        }
        // 封装返回
        AuthToken authToken = new AuthToken();
        authToken.setAccessToken((String) response.get("access_token"));
        authToken.setExpiresIn((Integer) response.get("expires_in"));
        authToken.setRefreshToken((String) response.get("refresh_token"));
        authToken.setTokenType((String) response.get("token_type"));
        authToken.setScope((String) response.get("scope"));
        return authToken;
    }
}

4. 自定义认证管理器 CustomerAuthProvider

@Slf4j
@Component
public class CustomAuthProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailService customUserDetailService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // LoginServiceImpl#getAccessToken方法中的loginJsonString
        String contentJsonStr
                = Objects.isNull(authentication.getPrincipal()) ? StringUtils.EMPTY : authentication.getName();
        // 通过authentication获取登录用户信息AuthUser
        AuthUser user = customUserDetailService.loadUserByContent(contentJsonStr);
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                user, authentication.getCredentials(), user.getAuthorities()
        );
        result.setDetails(authentication.getDetails());
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

5. 自定义UserDetailService实现类 CustomUserDetailService

作为UserDetailService实现类,原本应该继承UserDetailService接口,但是由于默认使用的账号密码验证方式会自动加载DaoAuthenticationProvider类,该类会自动扫描一个UserDetailService实现类并注入,如果存在多个UserDetailService实现类会注入失败,所以这里不继承该接口,直接实现功能,前面我们已经配置了一个RefreshTokenUserDetailServiceImpl

/**
 * 自定义的UserDetailService调用实现
 */
@Slf4j
@Service("customUserDetailService")
public class CustomUserDetailService{

    @Autowired
    private UserService userService;

    @Autowired
    private PolicyService policyService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private RolePolicyService rolePolicyService;
    
    /**
     * 通过用户ID获取相应的角色信息
     *
     * @param content 内容
     * @return NgsocUser
     * @throws UsernameNotFoundException
     */
    public AuthUser loadUserByContent(String content) throws UsernameNotFoundException {
        if (StringUtils.isBlank(content)) {
            throw new UsernameNotFoundException("exceptions.authentication.principal.error");
        }
        // content是loginJsonString,可以转为AuthJwtPrincipal
        AuthJwtPrincipal authJwtPrincipal = JSON.parseObject(content, AuthJwtPrincipal.class);
        String userId = authJwtPrincipal.getUserId();

        // 获取登录用户信息
        User user = userService.queryById(userId);
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("exceptions.authentication.principal.error");
        }

        // 获取登录用户的角色信息
        List<UserRole> userRoleList  = userService.getUserRoleByUserId(userId);
        List<String> roleIds = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());

        // 获取登录用户的权限信息
        List<Policy> policyList = policyService.getPolicyByRoleIds(roleIds);
        if (CollectionUtils.isEmpty(policyList)) {
            throw new UsernameNotFoundException("contact.administrator.to.assign.policy");
        }
        List<String> policyNames = policyList.stream().map(Policy::getName).collect(Collectors.toList());
        Set<GrantedAuthority> grantedAuthoritySet = policyList.stream()
                .map(policyEntity -> new SimpleGrantedAuthority(policyEntity.getName()))
                .collect(Collectors.toSet());

        // 存储本地用户信息
        UserInfo userInfo = new UserInfo();
        userInfo.setId(user.getId());
        userInfo.setUsername(user.getUsername());
        userInfo.setRoleIds(roleIds);
        userInfo.setPolicyName(policyNames);

        String encodePasswordStr = passwordEncoder.encode("password");
        return new AuthUser(userId, encodePasswordStr, userInfo, grantedAuthoritySet);
    }
}

6. 认证成功后的用户对象 AuthUser

/**
 * 认证成功后的用户对象
 */
@AllArgsConstructor
@NoArgsConstructor
public class AuthUser implements UserDetails {
    @Setter
    private String username;

    @Setter
    private String password;

    @Setter
    @Getter
    private UserInfo userInfo;

    /*登录用户具备的权限*/
    @Setter
    private Set<GrantedAuthority> authorities;

    public AuthUser(String username, String password){
        this.username = username;
        this.password = password;
    }

    public AuthUser(String username, String password, Set<GrantedAuthority> authorities){
        this(username,password);
        this.authorities = authorities;
    }

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

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

    @Override
    public String getUsername() {
        return 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;
    }
}

用户信息 UserInfo :

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
    private static final long serialVersionUID = 671647501342140183L;
    // 用户信息
    private String id;
    private String username;
    // 角色信息
    private List<String> roleIds;
    // 权限信息
    private List<String> policyName;
}

7. 安全配置类中添加自定义认证管理器 WebSecurityConfig

/**
 * - @EnableGlobalMethodSecurity:该注解是用来开启权限注解
 * - prePostEnabled = true:开启Spring Security提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 登陆认证相关
     */
    @Autowired
    private CustomAuthProvider customAuthProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    // 自定义AuthenticationManager:并没有在工厂中暴露出来
    // 使用AuthenticationManagerBuilder来自定义AuthenticationManager,覆盖默认的AuthenticationManager
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthProvider);
    }

    // 如需使用AuthenticationManager,则可以通过覆盖此方法,将configure(AuthenticationManagerBuilder)方法构造的AuthenticationManager暴露为Bean。
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

8. 启动项目测试登录功能

在这里插入图片描述

核心点debug过程:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k3s 离线部署指南

文章目录1. 简介2. Docker 手动部署镜像方式2.1 安装docker2.2 导入镜像2.3 安装 k3s2.4 查看3. Containerd 手动部署镜像方式3.1 导入镜像到 containerd 镜像列表3.2 授予可执行权限3.3 安装 K3s4. Containerd 私有镜像仓库方式4.1 配置 K3s 镜像仓库4.2 授予可执行权限4.3…

集合(Set)和有序集合(ZSet)的基本使用方法详解【Redis】

文章目录一. Redis中的集合(Set)1.1基本的一些操作1.1.1 smembers查看集合中的所有成员1.1.2 scard删除成员数量1.1.3 smove移动成员1.1.4 sinterstore 存储俩个集合的交集二.Redis中的有序集合(ZSet)2.1 基本的一些操作2.1.1 zadd添加1到多个成员2.1.2 zcount 返回指定分数区间…

【HarmonyOS】鸿蒙轻量级智能穿戴应用可以集成华为分析SDK吗?

1、问题描述 我们的项目是基于鸿蒙系统开发的轻量级智能穿戴应用&#xff0c;目前在做的主要是运动手表GT3。 我们在项目中使用了华为分析服务&#xff0c;但是在Build Hap时出现问题&#xff1a; 因此&#xff0c;我们想了解轻量级智能穿戴应用项目中是否能够集成华为分析服…

计算机毕业设计(附源码)python在线影评系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Leetcode-每日一题1106. 解析布尔表达式(DFS模拟栈)

题目链接&#xff1a;点击跳转 思路 方法一、DFS模拟栈 题目意思很简单让你去判断与或非布尔表达式的结果&#xff0c;我们可以看布尔表达式看成一棵树&#xff0c;需要我们解决的是从最底层的嵌套布尔表达式产生的结果不断向上的结果&#xff0c;如图&#xff1a; 既然他是…

云栖大会开源重磅升级!PolarDB-X v2.2: 企业级和国产化适配

2022 年云栖大会上&#xff0c;PolarDB-X 发布 2.2.0 版本&#xff0c;这是一个重要的里程碑版本&#xff0c;重点推出符合分布式数据库金融标准下的企业级和国产化适配&#xff0c;共包括八大核心特性&#xff0c;全面提升 PolarDB-X 分布式数据库在金融、通讯、政务等行业的普…

自定义分页器

文章目录自定义分页器1、分页推导2、分页器代码封装自定义分页器 1、分页推导 queryset对象支持切片操作 确定用户访问的页码 url?page1 current_pagerequest.GET.get(page,1)前端获取到 的都是字符串数据&#xff0c;需要类型转换 current_page request.GET.get(page,…

为什么C语言执行效率高,运行快?

目录 简述C语言由来 BCPL语言及代码B语言以及代码C语言 编程语言 机器语言汇编语言高级语言 C为何快速 简述 都说C语言编写的程序执行效率比较高&#xff0c;那么到底高在哪里&#xff0c;我们一块来学习学习。 C语言由来 C语言源自于BCPL、B两种语言。 BCPL语言以及代码…

django csrfMiddleware的一些理解跨站和跨域

术语 术语解释备注跨域两个 URL 的“协议主机名端口”3者只要有一个不一致http://www.taobao.com/和https://www.taobao.com/跨站两个 URL 的 eTLD1 不相同a.github.io 和 b.github.ioeTLDeffective top level domain &#xff08;有效顶级域名&#xff09;.com、.co.uk、.git…

第六节:数组的定义与使用【java】

目录 &#x1f4c3;1. 数组的基本概念 1.1 为什么要使用数组 1.2 什么是数组 1.3 数组的创建及初始化 1.4 数组的使用 &#x1f392;2. 数组是引用类型 2.1 初始JVM的内存分布 2.2 基本类型变量与引用类型变量的区别 2.3 认识 null 2.4 再谈引用变量 &#x1f4d6;3. …

Redis的RDB持久化配置以及数据恢复

文章目录Redis的RDB持久化配置以及数据恢复配置RDB持久化机制RDB持久化机制的工作流程基于RDB持久化机制的数据恢复Redis的RDB持久化配置以及数据恢复 配置RDB持久化机制 在 redis 的配置文件中找到如下内容&#xff1a; 以 save 60 10000 为例&#xff0c;表示每隔 60s&…

Springboot 整合与文件配置

哈喽~大家好&#xff0c;这篇看看Springboot 整合与文件配置。 &#x1f947;个人主页&#xff1a;个人主页 &#x1f948; 系列专栏&#xff1a;【Java框架】 &#x1f949;与这篇相关的文章&#xff1a; 【JAVAEE框架】MyBatis与Spring的整合&#xff0…

Unity-huatuo热更新调研

文章目录1. 相关资料2. 环境准备2.1 项目和编辑器版本2.2 安装huatuo插件2.3 在build settings里面勾选Export Project2.4 导出Android studio工程3. 验证热更新3.1 更改读取目录3.2 运行android apk3.3 热更新1. 相关资料 bilibili视频&#xff1a;https://www.bilibili.com/…

学院打卡第十四天

今天是一道困难题&#xff0c;难得有困难题&#xff01;&#xff01; 如题&#xff1a; 但是这个困难题感觉也不是很”困难“&#xff01;&#xff01;&#xff01; 算法思想&#xff1a; 第一感觉就是利用栈的思想&#xff0c;这种题做多了&#xff0c;一看见就是想到栈&…

2021年上半年软件设计师下午真题及答案解析(三)

阅读下列说明和图&#xff0c;回答问题1至问题3&#xff0c;将解答填入答题纸的对应栏内。 【说明】 某中医医院拟开发一套线上抓药APP&#xff0c;允许患者凭借该医院医生开具的处方线上抓药&#xff0c;并提供免费送药上门服务。该系统的主要功能描述如下&#xff1a; &#…

Kotlin编程实战——概述(01)

一 概述 Kotlin用于服务器开发Kotlin 进行 Android 开发Kotlin 用于 JavaScript 开发Kotlin 用于原生开发Kotlin 用于数据科学协程多平台 二 Kotlin用于服务器开发 2.1 原因 表现力可伸缩性互操作性迁移迁移工具学习曲线 2.2 Kotlin 进行服务器端开发的框架 Spring、Vert.…

Premiere 出现“该级别的帧大小/帧速率无效。请减小视频范围或帧速率,或者增加配置文件和级别,然后重试”的解决办法

有时候我们需要制作一定纵横比的视频&#xff0c;比如设置画面大小为1080(水平)1920(垂直)的竖屏尺寸&#xff0c;但是最近我导出视频时发现了 “该级别的帧大小/帧速率无效。请减小视频范围或帧速率&#xff0c;或者增加配置文件和级别&#xff0c;然后重试” 这个问题&#x…

使用插值法公式组成数字电路进行计算的计算机

使用插值法公式组成数字电路进行计算的计算机 使用插值法公式组成数字电路进行计算的计算机是一种可以使用插值法计算积分值&#xff0c;导数值&#xff0c;函数值的数字计算机&#xff0c;它由按键&#xff0c;液晶显示器&#xff0c;中央处理器组成。按键输入的程序保存在磁带…

【CUDA编程】CUDA内存模型

文章目录1. 内存结构2. GPU device内存2.1 寄存器(Registers)2.2 本地内存(Local Memory)2.3 共享内存(Shared Memory)2.4 常量内存(Constant Memory)2.5 纹理内存(Texture Memory)2.6 全局内存(Global Memory)3. CPU Host内存1. 内存结构 在CUDA中可编程内存的类型有&#xf…

文件管理的功能

文章目录什么是文件文件的属性文件内部的数据如何组织起来文件之间应该如何组织起来操作系统应该向上提供哪些功能从上往下看&#xff0c;文件应该如何存放在外存其他需要由操作系统实现的文件管理功能什么是文件 文件就是一组有意义的信息/数据集合 文件的属性 文件名&#x…