springboot集成oauth2+security+redis的配置


  • 导包
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
  • 数据库表配置
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  `archived` tinyint(1) NULL DEFAULT 0,
  `trusted` tinyint(1) NULL DEFAULT 0,
  `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'false',
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('user-clent', NULL, '$2a$10$DLARZJrTnLfUzK3XbVEuwuXJhBcr6t0ESsB9lf7Hg38x.kR0lzSwe', 'all', 'password,refresh_token', NULL, 'ROLE_WEB', NULL, NULL, '{}', '2021-12-09 09:32:41', 0, 0, 'true');

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_account
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_account`;
CREATE TABLE `t_sys_account`  (
  `account_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户编号',
  `username` char(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
  `password` char(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '密度',
  `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 1-正常',
  `active` tinyint(1) NULL DEFAULT NULL COMMENT '1-正常;0-禁用',
  PRIMARY KEY (`account_id`) USING BTREE,
  UNIQUE INDEX `uq_username`(`username`) USING BTREE COMMENT '用户名唯一索引'
) ENGINE = InnoDB AUTO_INCREMENT = 10002 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_account
-- ----------------------------
INSERT INTO `t_sys_account` VALUES (10001, 1, 'zhangsan', '1122', 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_account_2_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_account_2_role`;
CREATE TABLE `t_sys_account_2_role`  (
  `id` bigint(20) NOT NULL,
  `account_id` bigint(20) NOT NULL COMMENT '账号ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账号角色映射表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_account_2_role
-- ----------------------------
INSERT INTO `t_sys_account_2_role` VALUES (10001, 10001, 10001);

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_permission`;
CREATE TABLE `t_sys_permission`  (
  `perm_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `perm_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源代码',
  `perm_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源名称',
  `perm_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源路径',
  `perm_type` tinyint(2) NULL DEFAULT NULL COMMENT '授权范围 是否鉴权。1-菜单;2-普通资源',
  `perm_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源描述',
  PRIMARY KEY (`perm_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10002 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统权限资源' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_permission
-- ----------------------------
INSERT INTO `t_sys_permission` VALUES (10001, 'USER_ADMIN', '用户管理', '/user/**', 1, '用户管理');

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role`;
CREATE TABLE `t_sys_role`  (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  `role_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色代码',
  `active` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用:1-启用;0-禁用',
  `role_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`role_id`) USING BTREE,
  INDEX `idx_rolecode`(`role_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10002 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_role
-- ----------------------------
INSERT INTO `t_sys_role` VALUES (10001, '用户管理员', 'ROLE_ADMIN', 1, '系统管理员');

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_role_2_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role_2_permission`;
CREATE TABLE `t_sys_role_2_permission`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `perm_id` bigint(20) NOT NULL COMMENT '资源ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_roleid_permid`(`role_id`, `perm_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10002 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统权限资源角色映射表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_role_2_permission
-- ----------------------------
INSERT INTO `t_sys_role_2_permission` VALUES (10001, 10001, 10001);

SET FOREIGN_KEY_CHECKS = 1;
  • mapper 查询

1、根据用户名查询用户信息

    @Select({"SELECT t.account_id, t.username, t.`password`, t.active, t.`status`, t.user_id FROM t_sys_account t WHERE t.username=#{username} LIMIT 1"})
    SysUser getUserByName(@Param("username") String var1);

2、根据用户id查询用户角色信息

    @Select({"SELECT t1.role_id, t1.role_code, t1.role_name FROM t_sys_account_2_role t LEFT JOIN t_sys_role t1 ON t.role_id=t1.role_id WHERE t.account_id=${accountId} AND t1.active=1"})
    List<SysRole> getRoleByUser(@Param("accountId") long var1);

3、根据角色id查询权限信息

    @Select({"<script>SELECT p.perm_id, p.perm_code, p.perm_url  FROM t_sys_role_2_permission rp LEFT JOIN t_sys_permission p ON rp.perm_id = p.perm_id  WHERE rp.role_id IN <foreach collection=\"roleList\" item=\"r\" separator=\",\" open=\"(\" close=\")\">#{r}</foreach></script>"})
    List<SysPermission> listPermissionByRoleId(@Param("roleList") List<Long> var1);
  • security配置类-继承 WebSecurityConfigurerAdapter 类
package com.zl.securitytest.config;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

/**
 * spring security配置类
 *
 * @author z
 * @date 2021-12-01 16:06
 * @description:
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OauthWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Resource
    private OauthUserDetailsService oauthUserDetailsService;

    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * 配置用户身份验证
     *
     * @param auth
     * @Author z
     * @Date 2021/12/1 17:09
     * @Return void
     * @Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(oauthUserDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    /**
     * 注入 AuthenticationManager
     *
     * @param
     * @Author z
     * @Date 2021/12/7 14:50
     * @Return AuthenticationManager
     * @Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置拦截
     *
     * @param http
     * @Author z
     * @Date 2021/12/1 17:09
     * @Return void
     * @Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        http.csrf().disable();
        http.logout().logoutUrl("/oauth/logout");
    }

}
  • 自定义用户登录验证-实现 UserDetailsService 接口
package com.zl.securitytest.config;

import com.zl.securitytest.mapper.SysPermissionMapper;
import com.zl.securitytest.mapper.SysRoleUserMapper;
import com.zl.securitytest.mapper.SysUserMapper;
import com.zl.securitytest.pojo.SysPermission;
import com.zl.securitytest.pojo.SysRole;
import com.zl.securitytest.pojo.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 自定义用户登录
 *
 * @author z
 * @date 2021-12-03 17:31
 */
@Service
@Slf4j
public class OauthUserDetailsService implements UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;
    @Resource
    private SysRoleUserMapper sysRoleUserMapper;
    @Resource
    private SysPermissionMapper sysPermissionMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = this.sysUserMapper.getUserByName(username);
        if (null == user) {
            log.error("【{}】Account does not exist.", username);
            throw new UsernameNotFoundException(String.format("【%s】Account does not exist.", username));
        } else if (!user.getActive()) {
            log.error("【{}】The account is disabled.", username);
            throw new LockedException(String.format("【%s】The account is disabled.", username));
        } else if (!user.getStatus().equals(1)) {
            log.error("【{}】Account Status Is abnormal.", username);
            throw new LockedException(String.format("【%s】Account Status Is abnormal.", username));
        } else {
            user.setRoleList(this.getRoleList(user.getAccountId()));
            user.setAuthorities(this.listGrantedAuthority(user.getRoleList()));
            return user;
        }
    }

    // 获取角色
    private List<SysRole> getRoleList(long accountId) {
        List<SysRole> roleList = this.sysRoleUserMapper.getRoleByUser(accountId);
        Iterator iter = roleList.iterator();

        while (iter.hasNext()) {
            SysRole role = (SysRole) iter.next();
            if (StringUtils.isEmpty(role.getRoleCode())) {
                iter.remove();
            }

            if (role.getRoleCode().indexOf("ROLE_") != 0) {
                iter.remove();
            }
        }

        return roleList;
    }

    // 获取权限
    private List<GrantedAuthority> listGrantedAuthority(List<SysRole> roleList) {
        if (roleList.size() == 0) {
            return new ArrayList();
        } else {
            List<Long> roleIdList = (List) roleList.stream().map((a) -> {
                return a.getRoleId();
            }).collect(Collectors.toList());
            List<SysPermission> sysPermissions = this.sysPermissionMapper.listPermissionByRoleId(roleIdList);
            List<String> authorities = (List) sysPermissions.stream().map((a) -> {
                return a.getPermUrl();
            }).collect(Collectors.toList());
            roleList.forEach((a) -> {
                authorities.add(a.getRoleCode());
            });
            return AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities));
        }
    }
}
  • 自定义用户类-实现 UserDetails 接口
package com.zl.securitytest.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
 * 用户信息类
 *
 * @author z
 * @date 2021-12-07 15:31
 */
@NoArgsConstructor
@Data
public class SysUser implements UserDetails, Serializable {
    /**
     * id
     */
    private Long userId;
    /**
     * 用户id
     */
    private Long accountId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 用户密码
     */
    private String password;
    /**
     * 用户状态 1-正常
     */
    private Integer status;
    /**
     * 账户是否禁用 1-正常;0-禁用
     */
    private Boolean active;
    /**
     * 角色集合
     */
    private List<SysRole> roleList;
    /**
     * 权限集合
     */
    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> 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() {
        if (this.status == null) {
            return false;
        } else {
            return this.status.equals(1);
        }
    }

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

    @Override
    public boolean isEnabled() {
        return this.active;
    }
    @Override
    public String toString() {
        return "SysUser{" +
                "accountId=" + accountId+
                ", userId=" + userId +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", status=" + status +
                ", active=" + active +
                ", roleList=" + roleList +
                ", authorities=" + authorities +
                '}';
    }
}

实现UserDetails后注意方法里面返回属性值(遇到栈溢出)

  • 验证权限失败处理-实现 AccessDeniedHandler 接口
package com.zl.securitytest.config;

import com.alibaba.fastjson.JSONObject;
import com.zl.securitytest.common.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 验证权限失败处理类
 *
 * @author z
 * @date 2021-12-07 16:21
 */
@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        Result<Object> result = Result.failed(403, "没有访问权限");
        out.write(JSONObject.toJSONString(result));
        out.flush();
        out.close();
    }
}
  • oauth2 认证服务器配置-继承 AuthorizationServerConfigurerAdapter 类
package com.zl.securitytest.config;

import org.springframework.context.annotation.Configuration;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.annotation.Resource;

/**
 * 配置认证服务器
 *
 * @author z
 * @date 2021-12-07 14:54
 */
@Configuration
@EnableAuthorizationServer
public class OauthAuthorizationServerConfigurerAdapter extends AuthorizationServerConfigurerAdapter {
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private TokenStore tokenStore;
    @Resource
    private ApprovalStore approvalStore;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private JdbcClientDetailsService jdbcClientDetailsService;

    /**
     * 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
     * 1. ClientDetail加密方式
     * 2. allowFormAuthenticationForClients 允许表单认证。针对/oauth/token端点。
     * 3. 添加开发配置tokenEndpointAuthenticationFilters
     * 4. tokenKeyAccess、checkTokenAccess访问权限。
     * 默认过滤器:BasicAuthenticationFilter
     * 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
     * 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
     * 对以下的几个端点进行权限配置:
     * /oauth/authorize:授权端点
     * /oauth/token:令牌端点
     * /oauth/confirm_access:用户确认授权提交端点
     * /oauth/error:授权服务错误信息端点
     * /oauth/check_token:用于资源服务访问的令牌解析端点
     * /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许客户端访问oauth2授权接口,否则请求token返回401
        security.allowFormAuthenticationForClients()
                // 允许已授权的用户访问checkToken接口
                .checkTokenAccess("isAuthenticated()")
                // 允许已授权的用户访问获取token接口
                .tokenKeyAccess("isAuthenticated()")
                .passwordEncoder(this.passwordEncoder);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        BaseClientDetails baseClientDetails = new BaseClientDetails();
//        // 设置客户端id
//        baseClientDetails.setClientId("my_client_id");
//        // 设置秘钥
//        baseClientDetails.setClientSecret("my_test");
//        // 设置资源范围
//        baseClientDetails.setScope(Arrays.asList(new String[]{"all"}));
//        // 设置授权类型
//        baseClientDetails.setAuthorizedGrantTypes(Arrays.asList(new String[]{"password", "refresh_token"}));
//        // 添加角色
//        baseClientDetails.setAuthorities(Arrays.asList(new GrantedAuthority[]{new SimpleGrantedAuthority("ROLE_WEB")}));
//        // 设置自动批准
//        baseClientDetails.setAutoApproveScopes(Arrays.asList(new String[]{"true"}));
//        // 添加客户端配置
//        jdbcClientDetailsService.addClientDetails(baseClientDetails);

        // 配置客户端
        clients.withClientDetails(this.jdbcClientDetailsService);
    }

    /**
     * 装载类
     * 注入相关配置:
     * 1. 密码模式下配置认证管理器 AuthenticationManager
     * 2. 设置 AccessToken的存储介质tokenStore, 默认使用内存当做存储介质。
     * 3. userDetailsService注入
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.approvalStore(this.approvalStore).authenticationManager(this.authenticationManager).userDetailsService(userDetailsService).tokenStore(tokenStore);
    }
}
  • oauth2 资源服务器配置-继承 ResourceServerConfigurerAdapter 类
package com.zl.securitytest.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.annotation.Resource;

/**
 * 资源服务器
 *
 * @author z
 * @date 2021-12-07 16:29
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        prePostEnabled = true
)
public class OauthResourceServerConfigureAdapter extends ResourceServerConfigurerAdapter {
    @Resource
    private TokenStore tokenStore;
    @Resource
    private OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler;
    @Resource
    private SecurityAccessDeniedHandler securityAccessDeniedHandler;

    /**
     * 资源安全配置
     *
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("order").tokenStore(this.tokenStore).expressionHandler(this.oAuth2WebSecurityExpressionHandler);
    }

    /***
     * http安全配置
     *
     * @param http
     * @Author z
     * @Date 2021/12/8 17:45
     * @Return void
     * @Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        /**
         * anyRequest----匹配所有请求路径
         * access----springEl表达式结果为true时可以访问
         * anonymous----匿名可以访问
         * denyA1l----用户不能访问
         * fullyAuthenticated----用户完全认证可以访问(非remember-me下自动登录)
         * hasAnyAuthority----如果有参效,参数表示权限,则其中任何一个权限可以访问
         * hasAnyRole----如果有参数,参数表示角色,则其中任何一个角色可以访问
         * hasAuthority----如果有参数,参数表示权限,则其权限可以访问
         * hasIpAddress----如果有参数,参数表示IP她址,如果用户IP和参效匹配,则可以访问
         * hasRole----如果有参数,参数表示角色,则其角色可以访问
         * permitAll----用户可以任意访问
         * rememberMe----允许通过remember-me登录的用户访问
         * authenticated----用户登录后可访问
         */
        http.exceptionHandling().accessDeniedHandler(this.securityAccessDeniedHandler);
        http.authorizeRequests().antMatchers(new String[]{"/doc.html", "/webjars/**", "/swagger-resources", "/v2/api-docs-ext", "/v2/api-docs"}).permitAll();
        http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, new String[]{"/**"}).permitAll();
        http.authorizeRequests().antMatchers(new String[]{"/oauth/token"}).permitAll();
        http.authorizeRequests().antMatchers(new String[]{"/oauth/**"}).authenticated();
        // 鉴定权限
        http.authorizeRequests().anyRequest().access("@oauthAuthorityControlService.isAuthority(request, authentication)");
        http.csrf().disable();
    }
}
  • 用户权限对比
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zl.securitytest.config;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Iterator;

/**
 * 用户权限对比
 *
 * @author z
 */
@Component("oauthAuthorityControlService")
public class OauthAuthorityControlService {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    public OauthAuthorityControlService() {
    }

    public boolean isAuthority(HttpServletRequest request, Authentication authentication) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        String url = request.getRequestURI();
        Iterator var5 = authorities.iterator();

        GrantedAuthority authority;
        do {
            if (!var5.hasNext()) {
                return false;
            }

            authority = (GrantedAuthority) var5.next();
        } while (!this.antPathMatcher.match(authority.getAuthority(), url));

        return true;
    }
}
  • bean 资源配置
package com.zl.securitytest.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 授权资源配置
 *
 * @author z
 * @date 2021-12-07 15:04
 */
@Configuration
public class AuthorizationResourceConfig {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;
    @Resource
    private ApplicationContext applicationContext;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }

    /**
     * token 存储
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(this.redisConnectionFactory);
    }

    /**
     * 保存、检索 user
     *
     * @return
     */
    @Bean
    public ApprovalStore approvalStore() {
        TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();
        tokenApprovalStore.setTokenStore(this.tokenStore());
        return tokenApprovalStore;
    }

    /**
     * 从数据库中读取客户端请求模式
     *
     * @return
     */
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(this.applicationContext.getBean(DataSource.class));
        jdbcClientDetailsService.setPasswordEncoder(this.passwordEncoder());
        return jdbcClientDetailsService;
    }

    /**
     * web安全表达式处理器
     *
     * @return
     */
    @Bean
    public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler() {
        OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler = new OAuth2WebSecurityExpressionHandler();
        oAuth2WebSecurityExpressionHandler.setApplicationContext(this.applicationContext);
        return oAuth2WebSecurityExpressionHandler;
    }
}
  • redis 配置
package com.zl.securitytest.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis的配置类
 *
 * @author z
 * @date 2021-12-06 10:42
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
management:
  health:
    redis:
      enabled: false # 不会根据redis的默认初始配置, localhost:6379 尝试连接redis。
  • oauth2 请求令牌
http://localhost:8085/oauth/token?client_id=user-clent&client_secret=my_test&grant_type=password&username=zhangsan&password=1122

  • oauth2 刷新令牌
http://localhost:8085/oauth/token?client_id=user-clent&client_secret=my_test&grant_type=refresh_token&refresh_token=c10b0833-909d-4542-a845-7e22e3eb165e

  • oauth2 请求资源
http://localhost:8085/user/userInfo



免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM