博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security + OAuth系统环境搭建(一)
阅读量:6705 次
发布时间:2019-06-25

本文共 11121 字,大约阅读时间需要 37 分钟。

  最近在做权限管理系统的重构工作,系统基于Spring Security + OAuth架构,整体架构、技术和之前调研的结果差不多,架构调研时有在这篇博客做过简单记录“”,博客未更新,和定稿的架构稍有出入,图画的也有点问题。

  前面比较忙,搭建过程没有记录,平时也没有记笔记的习惯,有点惨,写到哪算哪吧~~~

一、添加pom依赖

  项目是在Spring Cloud基础上改造的,主要记录接入Spring Security + OAuth所做的工作。这里添加security和oauth的相关依赖:

org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
二、配置认证服务器

  Auth作为认证服务器:

  1、新建一个认证服务器配置类,代码如下:

@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {    @Autowired    private AuthenticationManager authenticationManager;    @Autowired    private UserDetailsService userDetailsService;    @Autowired    private RedisConnectionFactory redisConnectionFactory;    @Autowired    private DataSource dataSource;    /**     * 注册redisTokenStore,用于配置redis作为token仓库     *     * @return     */    @Bean    public RedisTokenStore tokenStore() {        return new RedisTokenStore(redisConnectionFactory);    }    /**     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。     *     * @param endpoints     * @throws Exception     */    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        endpoints.tokenStore(tokenStore())                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService);        endpoints.tokenServices(defaultTokenServices())                // 重复使用refresh_token ,指导refresh_token过期,才会产生新的refresh_token                .reuseRefreshTokens(true);    }    /**     * 配置使用客户端详情服务     *     * @param clients     * @throws Exception     */    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        clients.withClientDetails(clientDetailsService());    }    /**     * 用来配置令牌端点     *     * @param security     * @throws Exception     */    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        security.tokenKeyAccess("permitAll()");        security.checkTokenAccess("isAuthenticated()");        security.allowFormAuthenticationForClients();    }    /**     * 注册JdbcClientDetailsService,从数据库获取客户端详情     */    @Bean    public ClientDetailsService clientDetailsService() {        return new JdbcClientDetailsService(dataSource);    }    /**     * 配置token服务(仓库、客户端详情服务、是否支持refresh_token以及access_token、refresh_token的有效时间)     */    @Bean    @Primary    public DefaultTokenServices defaultTokenServices() {        DefaultTokenServices tokenServices = new DefaultTokenServices();        tokenServices.setTokenStore(tokenStore());        tokenServices.setSupportRefreshToken(true);        tokenServices.setClientDetailsService(clientDetailsService());        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24); // access_token有效期,默认12小时,-1表示永不过期        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//refresh_token有效期,默认30天        return tokenServices;    }}

  这里有个东西要注意,在http请求进来之后,首先框架自己会先校验token的正确性。系统oauth采用的是密码模式,熟悉oauth的应该知道,在请求头中必须要带上client_id和client_secret,client_id和client_secret会base64编码后放到请求头的Authorization字段中。这些信息可以在内存、数据库里面维护,这里用的是数据库,就是在clientDetailsService()方法中配置的内容,点开JdbcClientDetailsService的源码可以看到,oauth自己维护了一个oauth_client_details表,所以我们还需要在数据库创建相应的表以及数据。除了oauth_client_details还有维护token相关的表等等,这里没用上。

public JdbcClientDetailsService(DataSource dataSource) {        this.updateClientDetailsSql = DEFAULT_UPDATE_STATEMENT;        this.updateClientSecretSql = "update oauth_client_details set client_secret = ? where client_id = ?";        this.insertClientDetailsSql = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)";        this.selectClientDetailsSql = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?";        this.passwordEncoder = NoOpPasswordEncoder.getInstance();        Assert.notNull(dataSource, "DataSource required");        this.jdbcTemplate = new JdbcTemplate(dataSource);        this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(this.jdbcTemplate));    }
JdbcClientDetailsService

 

CREATE TABLE `oauth_client_details` (  `client_id` varchar(48) NOT NULL,  `resource_ids` varchar(256) DEFAULT NULL,  `client_secret` varchar(256) DEFAULT NULL,  `scope` varchar(256) DEFAULT NULL,  `authorized_grant_types` varchar(256) DEFAULT NULL,  `web_server_redirect_uri` varchar(256) DEFAULT NULL,  `authorities` varchar(256) DEFAULT NULL,  `access_token_validity` int(11) DEFAULT NULL,  `refresh_token_validity` int(11) DEFAULT NULL,  `additional_information` varchar(4096) DEFAULT NULL,  `autoapprove` varchar(256) DEFAULT NULL,  PRIMARY KEY (`client_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
oauth_client_details

 

  2、web配置类

@Configuration    @EnableWebSecurity    // 禁用Spring Boot默认的Security配置,配合@Configuration启用自定义配置public class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private UserDetailsService userDetailsService;    @Autowired    private MyAccessDecisionManager myAccessDecisionManager;    /*     * 加密工具     */    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    /*     * 认证管理器     */    @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    /*     * 身份验证配置,用于注入自定义身份验证Bean和密码校验规则     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService)                .passwordEncoder(passwordEncoder());    }    /**     * 无需权限校验直接放行的路径     */    private final String[] PATH_PASS = {            // 根据实际情况添加    };    /**     * Request层面的配置,对应XML Configuration中的
元素 */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(PATH_PASS).permitAll() .anyRequest().authenticated() .and() .formLogin() .and() .csrf().disable() .httpBasic(); // 将自定义的过滤器配置在FilterSecurityInterceptor之前 http.addFilterBefore(myFilterSecurityInterceptor(), FilterSecurityInterceptor.class); } /** * Web层面的配置,一般用来配置无需权限校验的路径,也可以在HttpSecurity中配置,但是在web.ignoring()中配置效率更高。 * web.ignoring()是一个忽略的过滤器,而HttpSecurity中定义了一个过滤器链,即使permitAll()放行还是会走所有的过滤器, * 直到最后一个过滤器FilterSecurityInterceptor认定是可以放行的,才能访问。 */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/favor.ioc"); } /** * 自定义的权限过滤器,后面再写 */ @Bean public MyFilterSecurityInterceptor myFilterSecurityInterceptor() { MyFilterSecurityInterceptor myFilterSecurityInterceptor = new MyFilterSecurityInterceptor(); myFilterSecurityInterceptor.setMyAccessDecisionManager(myAccessDecisionManager); return myFilterSecurityInterceptor; }}

   3、上面是一些基本配置,后面还需要自定义身份认证,实现UserDetailsService中的loadUserByUsername(String username)方法,在里面实现自己的身份校验逻辑。

@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        /*          * 自定义身份校验逻辑,以及根据具体业务做一些其他的事情,比如在redis维护详细的用户信息等等。         * 校验完毕之后将用户权限信息放到grantedAuthorities,最后根据具体校验结果生成User对象并返回。         */        Set
grantedAuthorities = new HashSet<>(); boolean enabled = true; // 可用性 :true:可用 false:不可用 boolean accountNonExpired = true; // 过期性 :true:没过期 false:过期 boolean credentialsNonExpired = true; // 有效性 :true:凭证有效 false:凭证无效 boolean accountNonLocked = true; // 锁定性 :true:未锁定 false:已锁定 User user = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities); return user; }}

   4、其实到这里基本的框架都已经差不多了,但是因为我们的Auth还作为资源服务器,还需要做对资源服务器的配置。这一块的配置就简单很多。

@Configuration@EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter{    /**     * 无需权限校验直接放行的路径     */    private final String[] PATH_PASS = {            // 根据实际情况添加    };        @Override    public void configure(HttpSecurity http) throws Exception {        http.formLogin()                .loginPage("/login")                .and()                .csrf().disable()                .authorizeRequests()                .antMatchers(AUTH_WHITELIST).permitAll()                .anyRequest().authenticated()                .and()                .httpBasic();    }}

 

  到这里,Auth的环境搭建基本上就算完成了。

 

三、配置资源服务器

  在我们的架构中,没有做服务间的权限控制,对外的鉴权工作统一放在网关中处理,所以网关也作为资源服务器。

@Configuration@EnableResourceServer@EnableWebSecuritypublic class WebSecurityConfig extends ResourceServerConfigurerAdapter {    @Autowired    private RedisConnectionFactory redisConnectionFactory;    @Autowired    private MyAccessDecisionManager myAccessDecisionManager;        /**     * 无需权限校验直接放行的路径     */    private final String[] PATH_PASS = {            // 根据实际情况添加    };    /**     * Request层面的配置,对应XML Configuration中的
元素 */ @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(AUTH_WHITELIST).permitAll() .and() .csrf().disable().exceptionHandling() // 将自定义的过滤器配置在FilterSecurityInterceptor之前 http.addFilterBefore(myFilterSecurityInterceptor(), FilterSecurityInterceptor.class); } /** * 资源服务Web层面的配置,这里主要配置了一些鉴权相关的入口,例如异常处理 */ @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler(); expressionHandler.setApplicationContext(applicationContext); return expressionHandler; } /** * 注册redisTokenStore,用于配置redis作为token仓库 * * @return */ @Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); } /** * 自定义的权限过滤器,后面再写 */ @Bean public MyFilterSecurityInterceptor myFilterSecurityInterceptor() { MyFilterSecurityInterceptor myFilterSecurityInterceptor = new MyFilterSecurityInterceptor(); myFilterSecurityInterceptor.setMyAccessDecisionManager(myAccessDecisionManager); return myFilterSecurityInterceptor; }}

 

  资源服务器的配置相对还是比较少的,因为需要在网关做鉴权,所以这里也配置了自定义过滤器。另外,还继承ZuulFilter类实现了过滤器,关于过滤器这块后面再统一写。

 

  到这呢,大体的环境搭建基本已经OK了,基本的功能能够使用了,后面的东西会稍微细一点,慢慢整理。

转载于:https://www.cnblogs.com/Mr-XiaoLiu/p/10051362.html

你可能感兴趣的文章
Linux常用命令汇总
查看>>
实现自定义的actuator端点
查看>>
爱情,是一场狡猾的战斗!
查看>>
rabbitmq在redhat5.5下搭建部署
查看>>
10.8.2,ssd开启trim
查看>>
Mac os X 必备的System Info 最新版
查看>>
配置管理小报100208:如何在不覆盖本地文件的情况下,获取文件的某个历史版本...
查看>>
android:name 的秘密
查看>>
开启简单的web服务器成为文件下载服务器
查看>>
shape详解
查看>>
轻松自动化---selenium-webdriver(python) (六)
查看>>
iPhone开发之UIWebview
查看>>
Mysql基本语法(-)
查看>>
Linux总线设备驱动模型简要分析
查看>>
OkHttp和Okio
查看>>
Chrome 假死
查看>>
割图片
查看>>
升级安装ubuntu12.04的感受
查看>>
360 evpp现代化C++11高性能TCP UDP HTTP网络库
查看>>
多例模式
查看>>