• springSecurity+Oauth2.0之授权模式(客户端、密码模式)


    SpringSecurity+Oauth2.0之授权模式

    1: 客户端模式:

              客户端模式(Client Credentials Grant)指客户端以自己的名义而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

    maven依赖:

    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.6.RELEASE</version> </dependency>

    创建Oauth2.0需要创建三个相关的表,直接使用官方的SQL脚本即可生成(不要修改表名和字段名)

    OAuth2 官方的项目中可以找到:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

    -- ----------------------------
    -- 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;
    ————————————————
    View Code

    配置yml(这里没用到数据库可以不配置):

    server:
      port: 8082
    
    
    mybatis:
      mapper-locations: classpath*:mapper/*Mapper.xml
      configuration:
          database-id: MySQL
         # 开启驼峰转换
          map-underscore-to-camel-case: true
          # spring boot集成mybatis的方式打印sql
    #      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    
    spring:
      application:
        name: oauth2-security-8082
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
          username: root
          password: 123456
          filters: stat
          # 设置最大数据库连接数,设为0为无限制
          maxActive: 20
          # 配置初始化大小、最小、最大
          initialSize: 1
          #  最大等待时间
          maxWait: 60000
          # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值
          minIdle: 1
          timeBetweenEvictionRunsMillis: 6000
    #      连接在池中保持空闲而不被回收的最小时间(毫秒)
          minEvictableIdleTimeMillis: 30000
          validationQuery: select 'x'
    #      对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false)
          testWhileIdle:  true
    #       当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接
          testOnBorrow: true
    #      当一个连接使用完归还到连接池时是否进行验证
          testOnReturn: false
    #      启用游标缓存,这个对数据库的性能提升很大
          poolPreparedStatements: true
    #        要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
          maxOpenPreparedStatements: 20
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 2000
    View Code

    配置认证服务器:

    /**
     * @Author dw
     * @ClassName AuthorizationServerConfig
     * @Description 配置认证服务器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error
     * @EnableAuthorizationServer // 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器
     * @Date 2020/4/20 14:47
     * @Version 1.0
     */
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    
        @Autowired
        private DataSource dataSource;
    
    
        @Bean
        public TokenStore tokenStore() {
            //使用内存中的 token store
    //        return new InMemoryTokenStore();
            //使用Jdbctoken store
            return new JdbcTokenStore(dataSource);
        }
    
        @Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }
    
        /**
         * @return
         * @Author dw
         * @Description 配置token的过期日期等
         * @Date 2020/4/23 18:32
         * @Param
         */
        @Bean
        @Primary
        public DefaultTokenServices defaultTokenServices() {
            DefaultTokenServices services = new DefaultTokenServices();
            //设置Token 20秒过期
            services.setAccessTokenValiditySeconds(20);
            //设置刷新token的过期时间
            services.setRefreshTokenValiditySeconds(666);
            services.setTokenStore(tokenStore());
            return services;
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 配置客户端, 用于client认证
            clients.withClientDetails(clientDetails());
    // 第一次使用的时候,需要配置客户端信息,或者手动添加客户端信息到数据库oauth_client_details表中
    //        clients.jdbc(dataSource)
    //                .withClient("myClient")
    //                .secret(new BCryptPasswordEncoder().encode("123456"))
    //                .authorizedGrantTypes("password", "refresh_token")//允许授权范围
    //                .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
    //                .scopes( "read", "write")
    //                .accessTokenValiditySeconds(7200)
    //                .refreshTokenValiditySeconds(7200);
        }
    
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //        endpoints.tokenStore(tokenStore())
            endpoints.tokenServices(defaultTokenServices())
                    //接收GET和POST
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()")
                    .allowFormAuthenticationForClients();//允许表单登录
        }
    
    
    }

     如果说是其他模式:

    .authorizedGrantTypes("authorization_code","client_credentials","password","refresh_token")//授权方式
    "authorization_code" : code模式
    "client_credentials": 客户端模式
    "password": 密码模式
    "implicit": 简化模式

    关于设置Token的过期时间如果配置的是从数据库读取,上面配置的Token过期时间将无效,可以在数据库中设置过期时间

    配置资源服务器:

    /**
     * @Author dw
     * @ClassName ResourceServerConfig
     * @Description 配置资源器
     * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
     * @Date 2020/4/20 15:03
     * @Version 1.0
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    //匹配需要资源认证路径
                    .antMatcher("/**")
                    .authorizeRequests()
                    //匹配不需要资源认证路径
                    .antMatchers("/oauth/**").permitAll()
                    .anyRequest().authenticated();
        }
    
    }

    配置springSecurity:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    // 配置密码的加密方式
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 用来配置拦截保护的请求
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    //匹配需要资源认证路径
                    .antMatcher("/**")
                    .authorizeRequests()
                    //匹配不需要资源认证路径
                    .antMatchers("/oauth/**").permitAll()
                    .anyRequest().authenticated();
        }
    
        public static void main(String[] args) {
            System.out.println(new BCryptPasswordEncoder().encode("123456"));
        }
    
    }

    Controller受保护的资源:

    /**
     * @Author dw
     * @ClassName HelloSecurityController
     * @Description
     * @Date 2020/4/14 11:34
     * @Version 1.0
     */
    @RestController
    public class HelloSecurityController {
    
        @GetMapping("/admin/hello")
        public String admin() {
            return "hello admin";
        }
    
        @GetMapping("/user/hello")
        public String user() {
            return "hello user";
        }
    
        @GetMapping("/db/hello")
        public String db() {
            return "hello db";
        }
    
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    
    }

    注意:这里可能需要你手动配置你的客户端到这张表中

    获取token:

    (A) 客户端向认证服务器进行身份认证,并要求一个访问令牌。请求的参数如下:
    • grant_type:表示授权类型,必选项,此处的值固定为"client_credentials"
    • client_id:表示客户端的ID,必选项
    • client_secret:客户端的密码,可选项
    • scope:表示申请的权限范围,可选项

     通过Token访问受保护的资源:

     2: 密码模式:

    这里分成资源服务器与授权服务器,即分离的形式来搭建,而不是资源服务器与授权服务器都在同一个项目中

    maven依赖(两个项目中都要添加):

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.6.RELEASE</version> </dependency>

    其他的依赖,可能项目中用得上:

     <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--操作数据库-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!-- MySQL 驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!-- Druid 数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.22</version>
            </dependency>
    
            <!-- region MyBatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>

    2.1:首先我们先搭建授权服务:

    yml配置:

    server:
      port: 8086
    
    
    mybatis:
      mapper-locations: classpath*:mapper/*Mapper.xml
      configuration:
          database-id: MySQL
         # 开启驼峰转换
          map-underscore-to-camel-case: true
          # spring boot集成mybatis的方式打印sql
    #      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    
    spring:
      application:
        name: oauth2-security-password-8086
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
          username: root
          password: 123456
          filters: stat
          # 设置最大数据库连接数,设为0为无限制
          maxActive: 20
          # 配置初始化大小、最小、最大
          initialSize: 1
          #  最大等待时间
          maxWait: 60000
          # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值
          minIdle: 1
          timeBetweenEvictionRunsMillis: 6000
    #      连接在池中保持空闲而不被回收的最小时间(毫秒)
          minEvictableIdleTimeMillis: 30000
          validationQuery: select 'x'
    #      对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false)
          testWhileIdle:  true
    #       当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接
          testOnBorrow: true
    #      当一个连接使用完归还到连接池时是否进行验证
          testOnReturn: false
    #      启用游标缓存,这个对数据库的性能提升很大
          poolPreparedStatements: true
    #        要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
          maxOpenPreparedStatements: 20
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 2000

    创建表:用户、角色、权限、用户角色表、角色权限表。

    CREATE TABLE `user` (
      `id` int(64) NOT NULL AUTO_INCREMENT,
      `user_name` varchar(32) DEFAULT NULL,
      `password` varchar(255) DEFAULT NULL,
      `enable` tinyint(4) DEFAULT NULL,
      `locked` tinyint(4) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(32) DEFAULT NULL,
      `description` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `resources` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `pattern` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    
    
    CREATE TABLE `user_role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `role_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `role_resource` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `role_id` int(11) DEFAULT NULL,
      `resource_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

    创建实体对象:

    package com.dw.study.entity;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    /**
     * (User)实体类
     *
     * @author makejava
     * @since 2020-04-14 13:10:12
     */
    public class User implements UserDetails, Serializable {
        private static final long serialVersionUID = 7673225091735750618L;
    
        private Integer id;
    
        private String userName;
    
        private String password;
    
        private boolean enable;
    
        private boolean locked;
    
        private List<Role> userRoles;
    
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (Role role : userRoles) {
                authorities.add(new SimpleGrantedAuthority(role.getName()));
            }
            return authorities;
        }
    
        @Override
        public String getUsername() {
            return userName;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return !locked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return enable;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        public boolean isEnable() {
            return enable;
        }
    
        public void setEnable(boolean enable) {
            this.enable = enable;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public boolean isLocked() {
            return locked;
        }
    
        public void setLocked(boolean locked) {
            this.locked = locked;
        }
    
        public List<Role> getUserRoles() {
            return userRoles;
        }
    
        public void setUserRoles(List<Role> userRoles) {
            this.userRoles = userRoles;
        }
    }
    public class Role implements Serializable {
        private static final long serialVersionUID = 825384782616737527L;
        
        private Integer id;
        
        private String name;
        
        private String description;
    
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    }
    public class Resources implements Serializable {
        private static final long serialVersionUID = -5840903661920488430L;
    
        private Integer id;
        
        private String pattern;
    
        private List<Role> roles;
    
    
        public List<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(List<Role> roles) {
            this.roles = roles;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getPattern() {
            return pattern;
        }
    
        public void setPattern(String pattern) {
            this.pattern = pattern;
        }
    }
    public class UserRole implements Serializable {
        private static final long serialVersionUID = -33173102844662867L;
        
        private Integer id;
        
        private Integer userId;
        
        private Integer roleId;
    
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    
        public Integer getRoleId() {
            return roleId;
        }
    
        public void setRoleId(Integer roleId) {
            this.roleId = roleId;
        }
    }

    创建dao

    @Repository
    public interface UserMapperDao {
    
      public User loadUserByUsername(String userName);
    
      public List<Role> getUserRolesByUid(Integer id);
    
    
    }

    创建mapper

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.dw.study.dao.ResourceMapperDao">
    
        <resultMap id="ResourcesMap" type="com.dw.study.entity.Resources">
            <id column="id" property="id"/>
            <result column="pattern" property="pattern"/>
            <collection property="roles" ofType="com.dw.study.entity.Role">
                <id column="roleId" property="id"/>
                <result column="name" property="name"/>
                <result column="description" property="description"/>
            </collection>
        </resultMap>
    
    
        <select id="getAllResources" resultMap="ResourcesMap">
            SELECT
             r.*,
             re.id AS roleId,
             re.`name`,
             re.description
            FROM resources AS r
            LEFT JOIN role_resource AS rr  ON r.id = rr.resource_id
            LEFT JOIN role AS re ON re.id = rr.role_id
        </select>
        
    </mapper>

    创建service

    @Service
    public class MyUserDetailServiceImpl implements UserDetailsService {
        @Autowired
        private UserMapperDao userMapperDao;
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapperDao.loadUserByUsername(username);
            String encodePassword = passwordEncoder.encode(user.getPassword());
            System.out.println("加密后的密码:" + encodePassword);
            user.setPassword(encodePassword);
            if (user == null) {
                throw new UsernameNotFoundException("账户不存在!");
            }
            List<Role> userRoles = userMapperDao.getUserRolesByUid(user.getId());
            user.setUserRoles(userRoles);
            return user;
        }
    }

    核心:配置认证服务器认证类

    package com.dw.study.oauth2Config;
    
    import com.dw.study.service.MyUserDetailServiceImpl;
    import com.dw.study.webConfig.CustomWebResponseExceptionTranslator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
    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 org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
    
    import javax.sql.DataSource;
    
    /**
     * @Author dw
     * @ClassName AuthorizationServerConfig
     * @Description 配置认证服务器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error
     * @EnableAuthorizationServer // 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器
     * @Date 2020/4/20 14:47
     * @Version 1.0
     */
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    
        @Autowired
        private DataSource dataSource;
    
        // 认证管理器,认证用户信息
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private MyUserDetailServiceImpl myUserDetailService;
    
        @Autowired
        private CustomWebResponseExceptionTranslator webResponseExceptionTranslator;
    
        @Bean
        public TokenStore tokenStore() {
            //使用内存中的 token store
    //        return new InMemoryTokenStore();
            //使用Jdbctoken store
            return new JdbcTokenStore(dataSource);
        }
    
        @Bean
        public ClientDetailsService clientDetails() {
            return new JdbcClientDetailsService(dataSource);
        }
    
        /**
         * @return
         * @Author dw
         * @Description 配置token的过期日期等
         * @Date 2020/4/23 18:32
         * @Param
         */
    //    @Bean
    //    @Primary
    //    public DefaultTokenServices defaultTokenServices() {
    //        DefaultTokenServices services = new DefaultTokenServices();
    //        // access token有效期2个小时
    //        services.setAccessTokenValiditySeconds(60 * 60 * 2);
    //        // refresh token有效期30天
    //        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
    //        // 支持使用refresh token刷新access token
    //        services.setSupportRefreshToken(true);
    //        // 允许重复使用refresh token
    //        services.setReuseRefreshToken(true);
    ////        services.setAuthenticationManager(authenticationManager);
    //        services.setTokenStore(tokenStore());
    //        return services;
    //    }
    
    
        private PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
            PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
            authenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(myUserDetailService));
            return authenticationProvider;
        }
    
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 配置客户端, 用于client认证
            clients.withClientDetails(clientDetails());
    // 第一次使用的时候,需要配置客户端信息,或者手动添加客户端信息到数据库oauth_client_details表中
    //        clients.jdbc(dataSource)
    //                .withClient("myClient")
    //                .secret(new BCryptPasswordEncoder().encode("123456"))
    //                .authorizedGrantTypes("password", "refresh_token")//允许授权范围
    //                .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
    //                .scopes( "read", "write")
    //                .accessTokenValiditySeconds(7200)
    //                .refreshTokenValiditySeconds(7200);
        }
    
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.tokenStore(tokenStore())
    //        endpoints.tokenServices(defaultTokenServices())
                    .authenticationManager(authenticationManager)
                    .userDetailsService(myUserDetailService)
                    //接收GET和POST
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                    .exceptionTranslator(webResponseExceptionTranslator);
    
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()")
                    .allowFormAuthenticationForClients();//允许表单登录
        }
    
    
    }

    如果不配置token的过期时间,默认是12个小时过期

    配置资源

    package com.dw.study.oauth2Config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    
    /**
     * @Author dw
     * @ClassName ResourceServerConfig
     * @Description 配置资源器
     * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
     * @Date 2020/4/20 15:03
     * @Version 1.0
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    //定义哪些url需要被保护  哪些不需要保护
                    .authorizeRequests()
                    //定义这两个链接不需要登录可访问
                    .antMatchers("/oauth/token" , "oauth/check_token").permitAll()
                    //定义所有的都不需要登录  目前是测试需要
    //             .antMatchers("/**").permitAll()
                    .anyRequest().authenticated() //其他的都需要登录
                    //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
                    .and()
                    .formLogin()
    //                .loginPage("/login") //如果未登录则跳转登录的页面   这儿可以控制登录成功和登录失败跳转的页面
                    .and()
                    .csrf().disable();//防止跨站请求  spring security中默认开启
        }
    
    }

    配置springSecurity

    package com.dw.study.securityConfig;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * @Author dw
     * @ClassName WebSecurityConfig
     * @Description
     * @Date 2020/4/20 15:07
     * @Version 1.0
     */
    
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean() ;
        }
    
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 配置用户签名服务 主要是user-details 机制,
         *
         * @param auth 签名管理器构造器,用于构建用户具体权限控制,这里交给oauth2的AuthorizationServer去处理
         * @throws Exception
         */
    //    @Override
    //    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //        auth.userDetailsService(myUserService)
    //                .passwordEncoder(passwordEncoder());
    //    }
    
    
        /**
         * 用来配置拦截保护的请求
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //定义哪些url需要被保护  哪些不需要保护
                 .authorizeRequests()
                    //定义这两个链接不需要登录可访问
                 .antMatchers("/oauth/token" , "oauth/check_token").permitAll()
                    //定义所有的都不需要登录  目前是测试需要
    //             .antMatchers("/**").permitAll()
                 .anyRequest().authenticated() //其他的都需要登录
                 //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
                 .and()
                 .formLogin()
    //                .loginPage("/login") //如果未登录则跳转登录的页面   这儿可以控制登录成功和登录失败跳转的页面
                 .and()
                 .csrf().disable();//防止跨站请求  spring security中默认开启
        }
    
    }

    配置全局异常处理类:

    package com.dw.study.webConfig;
    
    import org.bouncycastle.asn1.ocsp.ResponseData;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
    import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
    import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author dw
     * @Description web全局异常返回处理器
     * @Date 2020/4/24 14:14
     * @Param
     * @return
     */
    @Component
    public class CustomWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomWebResponseExceptionTranslator.class);
    
        @SuppressWarnings("PMD")
        @Override

    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        ResponseEntity responseEntity = super.translate(e);
    LOGGER.info("statusCode: " + responseEntity.getStatusCodeValue());
    OAuth2Exception oAuth2Exception = new OAuth2Exception("出错了!!");
    if(responseEntity.getStatusCodeValue() == 401){
    oAuth2Exception.addAdditionalInformation("code", "401");
    oAuth2Exception.addAdditionalInformation("data", "");
    oAuth2Exception.addAdditionalInformation("message", "用户名错误!");
    return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
    }else if(responseEntity.getStatusCodeValue() == 400){
    oAuth2Exception.addAdditionalInformation("code", "400");
    oAuth2Exception.addAdditionalInformation("data", "");
    oAuth2Exception.addAdditionalInformation("message", "用户密码错误!");
    return new ResponseEntity(oAuth2Exception, HttpStatus.BAD_REQUEST);
    }else {
    oAuth2Exception.addAdditionalInformation("code", String.valueOf(responseEntity.getStatusCodeValue()));
    oAuth2Exception.addAdditionalInformation("data", "");
    oAuth2Exception.addAdditionalInformation("message", "认证异常");
    return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
    }
    }
     }

    创建访问当前登录用户的接口:

    package com.dw.study.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.security.Principal;
    
    /**
     * @Author dw
     * @ClassName UserInfoController
     * @Description
     * @Date 2020/4/23 15:06
     * @Version 1.0
     */
    @RestController
    public class UserInfoController {
    
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping(value = "/userInfo")
        public Principal getUserInfo(Principal principal) {
            System.out.println("通过端口:" + serverPort + "获取用户信息:" + principal);
            return principal;
        }
    
    }

    好的到这里认证服务器就配置完了。

    下面配置资源服务器:

    1、pom同上;

     修改yml:

    server:
      port: 8087
    management:
    security:
    enabled: false
    security:
      oauth2:
        #token检查的授权端url
        authorization:
          check-token-access: http://127.0.0.1:8086/oauth/check_token
        #对应的注册码与注册密码
        client:
          id: oauth2-password
          client-secret: 123456
          userAuthorizationUri: http://127.0.0.1:8086/oauth/authorize
          access-token-uri: http://127.0.0.1:8086/oauth/token
          scope: all
          grant-type: password
    #      authentication-scheme: form
        #获得授权端的当前用户信息url
        resource:
          user-info-uri: http://127.0.0.1:8086/userInfo

    定义资源服务器配置:

    package com.dw.study.oauth2Config;
    
    import org.springframework.context.annotation.Configuration;
    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.EnableOAuth2Client;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.web.cors.CorsConfiguration;
    
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @Author dw
     * @ClassName ResourceServerConfig
     * @Description 配置资源器
     * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
     * @EnableGlobalMethodSecurity : 开启三种可以在方法上面加权限控制的注解
     * @Date 2020/4/20 15:03
     * @Version 1.0
     */
    @Configuration
    @EnableResourceServer
    @EnableOAuth2Client
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    //匹配需要资源认证路径
                    .antMatcher("/**")
                    .authorizeRequests()
                    //匹配不需要资源认证路径
                    .antMatchers("/oauth/**").permitAll()
                    .anyRequest().authenticated();
        }
    
    }

    定义获取用户信息的util:

    package com.dw.study.Utils;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    
    import java.util.LinkedHashMap;
    
    /**
     * @Author dw
     * @ClassName UserUtil
     * @Description
     * @Date 2020/4/24 17:05
     * @Version 1.0
     */
    public class UserUtil {
    
        private UserUtil (){}
    
        public static LinkedHashMap getUser(){
            return getUserDetails();
        }
    
        private static LinkedHashMap getUserDetails() {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            OAuth2Authentication oAuth2Authentication = (OAuth2Authentication)authentication;
            LinkedHashMap userDetails = (LinkedHashMap) oAuth2Authentication.getUserAuthentication().getDetails();
            LinkedHashMap principal = (LinkedHashMap) userDetails.get("principal");
            // 这里需要手动传换成User对象
            return principal;
        }
    
    }

    最后新建user、role类,同上面一样

    建controller

    @RestController
    @Slf4j
    public class MyController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping("/get")
        public String get(){
            return "成功获取到资源: port: " + serverPort;
        }
    
        @GetMapping("/userInfo")
        public Object getUserInfo(){
            LinkedHashMap user = UserUtil.getUser();
            return user;
        }
    
    }

    好的,配置完成。下面测试

     1:oauth_client_detais这张表中配置我们的客户端信息

     启动两个项目,然后使用postman:

    1: 获取token:

     2:刷新token获取token:

     访问用户信息:

    关于使用HTTP请求头  Authorization,不直接用  client_id 和  client_secret

     客户端发送http请求流程:
    服务器发现配置了http auth,于是检查request里面有没有"Authorization"的http header
    如果有,则判断Authorization里面的内容是否在用户列表里面,Authorization header 的典型数据为"Authorization: Basic jdhaHY0=",

    其中Basic   表示基础认证,

    jdhaHY0=   是base64编码的"user:passwd"字符串。

    如果没有,或者用户密码不对,则返回http code 401页面给客户端。

    base64在线编码j解码工具:http://tool.chinaz.com/Tools/Base64.aspx

     Header:

     

    Authorization   : Basic YnJvd3Nlcjo=

     配置跨域访问问题:

    /**
     * 跨域访问配置
     */
    @Configuration
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class CORSFilter implements Filter {
    
        public static final String CREDENTIALS_NAME = "Access-Control-Allow-Credentials";
        public static final String ORIGIN_NAME = "Access-Control-Allow-Origin";
        public static final String METHODS_NAME = "Access-Control-Allow-Methods";
        public static final String HEADERS_NAME = "Access-Control-Allow-Headers";
        public static final String MAX_AGE_NAME = "Access-Control-Max-Age";
        @Override
        public void destroy() {
    
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) resp;
            HttpServletRequest request = (HttpServletRequest) req;
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
    
            if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                response.setStatus(HttpServletResponse.SC_OK);
            } else {
                chain.doFilter(req, resp);
            }
    
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    }

     

  • 相关阅读:
    web前端笔记1
    前端与后台交互所需技术
    js的HTML属性操作
    浮动塌陷
    前端与后端的交互(定义接口)
    AjAX(第3章 Ajax的简单例子(Ajax+PHP)
    AjAX(简单概要介绍)
    Bootstrap 学习之js插件(折叠(collapse)插件)
    Net core 项目 EF CodeFist 多重外键约束问题
    对VS 2017中ASP.NET Core项目解决:Add-Migration : 无法将“Add-Migration”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
  • 原文地址:https://www.cnblogs.com/dw3306/p/12745900.html
Copyright © 2020-2023  润新知