zoukankan      html  css  js  c++  java
  • Springboot2.0 + OAuth2.0之密码模式之单项目集成

    概述:Spring OAuth2.0 是Spring Security中的模块,提供项目安全认证(包括身份,权限,角色认证等)。其作用和 shiro 差不多,同属于安全框架。但在使用角度个人觉得 shiro更易理解更易上手,更多差别还需了解。

    其中OAuth2为我们提供了四种授权方式:

    1、授权码模式(authorization code)
    2、简化模式(implicit)
    3、密码模式(resource owner password credentials)
    4、客户端模式(client credentials)

    而较常用的则为密码模式授权码模式,而授权码模式又是最为安全的模式,复杂程度成正比。完整的项目结构分为:客户端服务,认证服务,资源服务。客户端需要访问资源服务的资源时,则需要得到认证服务的认证。

    一、密码模式

    (1)单项目集成(即认证服务即为资源服务)其项目结构如下:

    此项目属于Maven项目。

    •  配置认证服务器

    首先需要配置认证服务所必须的配置,为 AuthorizationServerConfig 和 WebSecurityConfig 两个配置文件,其详情如下:

    package com.liuzj.oauth2server.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    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.client.JdbcClientDetailsService;
    import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
    
    import javax.sql.DataSource;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器,
     * 提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error
     *
     * @author liuzj
     * @date  2019-01-15
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        /**
         * webSecurityConfig 中配置的AuthenticationManager
         */
        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;
    
        /**
         * 此项目使用数据库保存 token 等信息所以要配置数据源
         */
        @Autowired
        private DataSource dataSource;
    
        /**
         * webSecurityConfig 中配置的 userDetailsService
         */
        @Autowired
        @Qualifier("userDetailsServiceImpl")
        private UserDetailsService userDetailsService;
    
        /**
         * webSecurityConfig 中配置的 passwordEncoder(使用MD5加密)
         */
        @Autowired
        PasswordEncoder passwordEncoder;
    
        @Bean
        public TokenStore tokenStore() {
    
            //使用内存中的 token store
    //        return new InMemoryTokenStore();
    
            //使用Jdbctoken store
            return new JdbcTokenStore(dataSource);
        }
    
        /**
         * 对 oauth_client_details 表的一些操作
         *
         * @return ClientDetailsService
         */
        @Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }
    
        @Autowired
        TokenStore tokenStore;
    
        @Autowired
        ClientDetailsService clientDetailsService;
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.jdbc(dataSource); 
              // 请求token的时候会将client_id,client_secret等信息保存到 oauth_client_details 表中,所以需要手动创建该表
               // 注意:以下注释的代码在请求了一次 token 之后则可以注释掉,否则如果不换 client 名字的话会因为主键冲突无法插入 client 信息。也可以一开始就注释,手动添加记录到数据库
    // .withClient("client") // .secret(passwordEncoder.encode("123456")) // .authorizedGrantTypes("authorization_code", "refresh_token", // "password", "implicit") // 四种认证模式 // .scopes("all") // .authorities("ROLE_admin","ROLE_user") // .redirectUris("http://www.baidu.com") // .accessTokenValiditySeconds(120000) // .refreshTokenValiditySeconds(50000); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()") //允许check_token访问 .checkTokenAccess("permitAll()") //允许表单登录 .allowFormAuthenticationForClients(); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); endpoints.userDetailsService(userDetailsService); endpoints.setClientDetailsService(clientDetailsService); //配置TokenServices参数 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(true); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // access_token 过期时间:5s tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // refresh_token 过期时间,默认不过期 // tokenServices.setReuseRefreshToken(true); // tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.SECONDS.toSeconds(20)); endpoints.tokenServices(tokenServices); } @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setSupportRefreshToken(true); tokenServices.setTokenStore(tokenStore); return tokenServices; } }
    package com.liuzj.oauth2server.config;
    
    import com.liuzj.oauth2server.utils.MD5Util;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    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.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.password.PasswordEncoder;
    
    /**
     * 安全配置
     *
     * @author liuzj
     * @date  2019-01-15
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * userDetailsService 获取token的时候对用户进行一些自定义过滤,并将保存用户信息(用户名,密码,角色等)
         */
        @Autowired
        @Qualifier("userDetailsServiceImpl")
        private UserDetailsService userDetailsService;
    
        /**
         * 使用MD5对client_secreat进行加密,可以使用默认的加密方式也可以自定义,这里使用MD5加密方式
         *
         * @return PasswordEncoder
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new PasswordEncoder() {
                @Override
                public String encode(CharSequence charSequence) {
                    return MD5Util.encodeMD5(String.valueOf(charSequence));
                }
    
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    return s.equals(MD5Util.encodeMD5(String.valueOf(charSequence)));
                }
            };
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * 配置用户签名服务 主要是user-details 机制,
         *
         * @param auth 签名管理器构造器,用于构建用户具体权限控制
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder());
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http
                    .requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
                    .and()
                    .authorizeRequests()
                    .antMatchers("/oauth/**").authenticated()
                    .and()
                    .formLogin().permitAll(); //新增login form支持用户登录及授权
        }
    }

    以上有一个 UserDetailsService 这个接口是提供出来我们自己实现的,在实现代码中可以自定义一下过滤规则,比如判断用户的合法性,具体如下:

    package com.liuzj.oauth2server.config.selfauthor;
    
    import com.liuzj.oauth2server.domain.User;
    import com.liuzj.oauth2server.repositories.UserRepository;
    import com.liuzj.oauth2server.utils.MD5Util;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    /**
     * 进行登录用户自定义过滤
     *
     * @author liuzj
     * @date 2019-01-15
     */
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
    
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(final String username)
                throws UsernameNotFoundException {
    
            User user = userRepository.findByUserName(username);
    
            logger.info("loadUserByUsername username=" + username);
         // 如果用户不存在则认证失败
            if(user == null){
                throw new UsernameNotFoundException(username + " not found");
            }
    
            // 注意:此处的密码记得要进行加密,因为在前面配置的时候是使用了 MD5 加密,所以这里也要进行加密
            return new UserInfo(username, MD5Util.encodeMD5(user.getPassword()),user.getRole());
        }
    
    }

    以上只是将主要的配置附上,还有 user 表以及 集成 mybatis 等步骤没有写出,请自行倒腾。。。注意:user 对象需要实现 Serializable 接口(可序列化),因为在认证的时候该对象是需要进行IO操作的。

    配置好认证服务之后跑如下 SQL 创建几张必须的表(存储 token 以及 client 等信息),因为本案例是使用数据库存储 token 的,当然也可以存在内存以及通过 jwt 方式:

    -- ----------------------------
    -- Table structure for oauth_access_token
    -- ----------------------------
    DROP TABLE IF EXISTS `oauth_access_token`;
    CREATE TABLE `oauth_access_token`  (
      `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `token` blob NULL,
      `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
      `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `authentication` blob NULL,
      `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`authentication_id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
     
    -- ----------------------------
    -- Table structure for oauth_client_details
    -- ----------------------------
    DROP TABLE IF EXISTS `oauth_client_details`;
    CREATE TABLE `oauth_client_details`  (
      `client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
      `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `authorities` varchar(256) 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` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`client_id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
     
    -- ----------------------------
    -- Table structure for oauth_refresh_token
    -- ----------------------------
    DROP TABLE IF EXISTS `oauth_refresh_token`;
    CREATE TABLE `oauth_refresh_token`  (
      `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `token` blob NULL,
      `authentication` blob NULL
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

    -- ----------------------------
    -- Table structure for user
    -- ----------------------------
    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
      `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名称',
      `password` varchar(255) NOT NULL DEFAULT '0' COMMENT '密码',
      `role` varchar(255) NOT NULL COMMENT '角色',
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

    到此,启动项目,访问连接:http://localhost:8001/oauth/token?username=liuzj&password=123&grant_type=password&client_id=client&client_secret=123456 (POST)

     正常情况下如下返回:

    这样就成功通过 账号和密码获取了 token。还可以尝试如下操作:

    检查 token :http://localhost:8001/oauth/check_token?token=f0cb83c3-6dd7-4c63-ab9f-2bdf3d492b46 (get)

    刷新 token: http://localhost:8001/oauth/token?grant_type=refresh_token&refresh_token=cd08de58-b91f-45c6-b165-6b8bd4b7bfdc&client_id=client&client_secret=123456 (post)


     • 配置资源服务器

    首先配置资源服务器专有配置,如下:

    package com.liuzj.oauth2server.config;
    
    import org.springframework.context.annotation.Configuration;
    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;
    
    /**
     * 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定受资源服务器保护的资源链接
     * 默认情况下spring security oauth2的http配置会被WebSecurityConfigurerAdapter的配置覆盖
     *
     * @author liuzj
     * @date 2019-01-15
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable()//禁用了csrf(跨站请求伪造)功能
                    .authorizeRequests()//限定签名成功的请求
                    //必须认证过后才可以访问;注意:hasAnyRole 会默认加上ROLE_前缀,而hasAuthority不会加前缀
                    .antMatchers("/decision/**","/govern/**").hasAnyRole("user") // 在角色过滤的时候需要注意user角色需要加角色前缀
                    .antMatchers("/admin/**").hasRole("admin")
                    .antMatchers("/test/**").authenticated()
                    // 免验证请求
                    .antMatchers("/oauth/**").permitAll();
        }
    
    }

    配置好如上配置就搞定了。重启项目测试访问项目接口(接口自行去码):

    如果直接访问它便会提示:此资源访问需要认证。所以得先获取 token(上面有讲)带上 token 即可访问接口,如下:

     到此密码认证告一段落。


  • 相关阅读:
    JSP和Servlet面试题
    HTML5实现仪表盘、温度计等插件实用源码
    爬虫(GET)——爬取多页的html
    爬虫(GET)——传递要查询的关键字
    爬虫(GET)——add_header()和get_header()
    爬虫(GET)——爬baidu.com主页
    js闭包
    python第一天
    android学习笔记三
    android学习笔记二
  • 原文地址:https://www.cnblogs.com/lzj123/p/10278119.html
Copyright © 2011-2022 走看看