SpringSecurity+Oauth2.0之授权模式
1: 客户端模式:
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。
maven依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--security-oauth2--> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </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-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>
创建Oauth2.0需要创建三个相关的表,直接使用官方的SQL脚本即可生成(不要修改表名和字段名)
OAuth2 官方的项目中可以找到:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
扫描二维码关注公众号,回复:
11016489 查看本文章
-- ---------------------------- -- 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; ————————————————
配置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: my-security-study 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 `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 `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 `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;
配置认证服务器:
/** * @Author dw * @ClassName AuthorizationServerConfig * @Description 配置认证服务器 * @Date 2020/4/20 14:47 * @Version 1.0 */ @Configuration @EnableAuthorizationServer // 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器// // 提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; @Autowired private MyUserServiceImpl myUserService; @Bean public TokenStore tokenStore() { // return new InMemoryTokenStore(); //使用内存中的 token store return new JdbcTokenStore(dataSource); ///使用Jdbctoken store } @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 配置客户端, 用于client认证 clients.withClientDetails(clientDetails()); // 第一次使用的时候,需要配置客户端信息 // clients.jdbc(dataSource) // .withClient("myClient") // .secret(new BCryptPasswordEncoder().encode("123456")) // .authorizedGrantTypes("refresh_token","client_credentials")//允许授权范围 // .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限 // .scopes( "read", "write") // .accessTokenValiditySeconds(7200) // .refreshTokenValiditySeconds(7200); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) .userDetailsService(myUserService);//必须设置 UserDetailsService 否则刷新token 时会报错 } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients();//允许表单登录 } }
如果说是其他模式:
.authorizedGrantTypes("authorization_code","client_credentials","password","refresh_token")//授权方式
配置资源服务器:
/** * @Author dw * @ClassName ResourceServerConfig * @Description 配置资源器 * @Date 2020/4/20 15:03 * @Version 1.0 */ @Configuration @EnableResourceServer //这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接 public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable()//禁用了 csrf 功能 .authorizeRequests()//限定签名成功的请求 .antMatchers("/decision/**","/govern/**").hasAnyRole("USER","ADMIN") .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/test/**") .authenticated()//必须认证过后才可以访问 .anyRequest().permitAll()//其他没有限定的请求,允许随意访问 .and().anonymous();//对于没有配置权限的其他请求允许匿名访问 } }
配置springSecurity:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserServiceImpl myUserService; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean() ; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 配置用户签名服务 主要是user-details 机制, * * @param auth 签名管理器构造器,用于构建用户具体权限控制 * @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 { //不拦截 oauth 开放的资源 http.csrf().disable(); http.requestMatchers()//使HttpSecurity接收以"/login/","/oauth/"开头请求。 .antMatchers("/oauth/**", "/login/**", "/logout/**") .and() .authorizeRequests() .antMatchers("/oauth/**").authenticated() .and() .formLogin(); } }
定义MyUserDetailService
/** * @Author dw * @ClassName UserService * @Description * @Date 2020/4/14 13:26 * @Version 1.0 */ @Service public class MyUserServiceImpl 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; } }
定义User实体:
/** * (User)实体类 * * @author makejava * @since 2020-04-14 13:10:12 */ public class User implements UserDetails { 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; } 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; } }
Role:
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; } }
userRole:
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; } }
定义userMapper:
/** * @Author dw * @ClassName UserMapperDao * @Description * @Date 2020/4/14 13:11 * @Version 1.0 */ @Repository public interface UserMapperDao { public User loadUserByUsername(String userName); public List<Role> getUserRolesByUid(Integer id); }
获取token:
(A) 客户端向认证服务器进行身份认证,并要求一个访问令牌。请求的参数如下:
- response_type:表示授权类型,必选项,此处的值固定为"client_credentials"
- client_id:表示客户端的ID,必选项
- client_secret:客户端的密码,可选项
- scope:表示申请的权限范围,可选项
POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_id=s6BhdRkqt3 &client_secret=123456&scope=test
返回: