环境准备

controller

@RestController
@RequestMapping("/api/admin")
public class AdminController {@GetMapping("/hello")public String hello(){return "hello! this is admin page";}
}/**----------------------分割线------------------------*/@RestController
@RequestMapping("/api/user")
public class UserController {@GetMapping("/hello")public String hello(){return "hello! this is user page!";}
}/**----------------------分割线------------------------*/@RestController
@RequestMapping("/api/public")
public class PublicController {@GetMapping("/hello")public String hello(){return "hello! this is public page";}
}

资源权限配置

@EnableWebSecurity
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/api/user/**").hasAnyRole("user")  //user 角色访问/api/user/开头的路由.antMatchers("/api/admin/**").hasAnyRole("admin") //admin角色访问/api/admin/开头的路由.antMatchers("/api/public/**").permitAll()                 //允许所有可以访问/api/public/开头的路由.and().formLogin();}
}

antMatchers()是一个采用ANT模式的URL匹配器:

  • * 表示匹配0或任意数量的字符
  • ** 表示匹配0或者更多的目录。antMatchers("/admin/api/**")相当于匹配了/admin/api/下的所有API。

配置默认用户,密码和角色信息

#默认登录用户
spring.security.user.name=user
#默认user用户密码
spring.security.user.password=user
#默认user用户所属角色
spring.security.user.roles=user

启动服务进行验证

直接方访问localhost:8080/api/public/hello,没问题,正常访问:

访问localhost:8080/api/user/hello,跳转到了登录验证页面:

正确使用user用户进行登录后,也能够正常访问:

访问localhost:8080/api/admin/hello,跳转到了登录验证页面:

此时正确使用user用户进行登录后,发现提示了403

页面显示403错误,表示该用户授权失败,401代表该用户认证失败;本次访问已经通过了认证环节,只是在授权的时候被驳回了。

基于内存的多用户支持

到目前为止,我们仍然只有一个可登录的用户,怎样引入多用户呢?非常简单,我们只需实现一个自定义的UserDetailsService即可:

    @Beanpublic UserDetailsService userDetailsService(){UserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();//创建用户user01,密码user01,角色useruserDetailsManager.createUser(User.withUsername("user01").password("user01").roles("user").build());//创建用户admin01,密码admin01,角色adminuserDetailsManager.createUser(User.withUsername("admin01").password("admin01").roles("admin").build());return userDetailsManager;}

其中InMemoryUserDetailsManagerUserDetailManager的实现类,它将用户数据源寄存在内存里,在一些不需要引入数据库这种重数据源的系统中很有帮助。
UserDetailManager 继承了 UserDetailService;

重启服务,使用新创建的用户admin01去访问localhost:8080/api/admin/hello,我们可以发现,依旧未能够正确认证并授权:

这块儿在陈木鑫老师的书中是已经能够正确认证并授权通过了,现在为什么没有成功呢?
因为Spring security 5.0中新增了多种加密方式,也改变了密码的格式。详见:https://blog.csdn.net/canon_in_d_major/article/details/79675033

我们这里针对性对userDetailsService进行修改,指明使用默认的passwordEncoder

    @Beanpublic UserDetailsService userDetailsService(){InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();//创建用户user01,密码user01,角色useruserDetailsManager.createUser(User.withDefaultPasswordEncoder().username("user01").password("user01").roles("user").build());//创建用户admin01,密码admin01,角色adminuserDetailsManager.createUser(User.withDefaultPasswordEncoder().username("admin01").password("admin01").roles("admin").build());return userDetailsManager;}

这样我们就可以正确的认证访问了:

基于默认数据库模型的认证与授权

除了InMemoryUserDetailsManager,Spring Security还提供另一个UserDetailsService实现类:JdbcUserDetailsManager
JdbcUserDetailsManager帮助我们以JDBC的方式对接数据库和Spring Security。

JdbcUserDetailsManager设定了一个默认的数据库模型,只要遵从这个模型,在简便性上,JdbcUserDetailsManager甚至可以媲美InMemoryUserDetailsManager

准备数据库

我这里使用的是PostgreSQL数据库,在这块儿使用其他数据库(例如MySQL)都是一样的,这块儿看个人爱好吧。

(1)在工程中引入jdbcPostgreSQL两个必要依赖:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>

(2)在application.properties配置文件中配置数据库连接信息:

#数据库连接信息
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/springsecuritydemo?schema=public
spring.datasource.data-username=postgres
spring.datasource.data-password=aaaaaa

(3)预制表结构和数据
JdbcUserDetailsManager设定了一个默认的数据库模型,Spring Security将该模型定义在/org/springframework/security/core/userdetails/jdbc/users.ddl内:

create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);

JdbcUserDetailsManager需要两个表,其中users表用来存放用户名、密码和是否可用三个信息,authorities表用来存放用户名及其权限的对应关系。

我们现在使用sql创建这两张表,在Pg数据库中执行上面的语句,我们发现以下错误:

因为该语句是用hsqldb创建的,而PostgreSQL不支持
varchar_ignorecase这种类型。怎么办呢?很简单,将varchar_ignorecase改为PostgreSQL支持的varchar即可:

create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null);
create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);




配置完成后,重启环境,正常启动。

编码实现

下面我们修改一下userDetailService bean,使用JdbcUserDetailsManager实现,让Spring Security使用数据库来管理用户:

    @Beanpublic UserDetailsService userDetailsService(DataSource dataSource){JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager();userDetailsManager.setDataSource(dataSource);//创建用户user01,密码user01,角色useruserDetailsManager.createUser(User.withDefaultPasswordEncoder().username("user01").password("user01").roles("user").build());//创建用户admin01,密码admin01,角色adminuserDetailsManager.createUser(User.withDefaultPasswordEncoder().username("admin01").password("admin01").roles("admin").build());return userDetailsManager;}

JdbcUserDetailsManagerInMemoryUserDetailsManager在用法上没有太大区别,只是多了设置DataSource的环节。Spring Security 通过DataSource执行设定好的命令。例如,此处的createUser函数实际上就是执行了下面的SQL语句:

insert into users (username,password,enabled) values(?,?,?)

查看 JdbcUserDetailsManager 的源代码可以看到更多定义好的 SQL 语句,诸如deleteUserSqlupdateUserSql等,这些都是JdbcUserDetailsManager与数据库实际交互的形式。当然,JdbcUserDetailsManager 也允许我们在特殊情况下自定义这些 SQL 语句,如有必要,调用对应的setXxxSql方法即可。

现在重启服务,我们发现看看Spring Security在数据库中生成了下面这些数据:
users表:

authorities表:

重启服务后,使用user01用户和admin01用户都能够正常合理访问接口,与预期的行为一致。

到目前为止,一切都工作得很好,但是只要我们重启服务,应用就会报错。这是因为users表在创建语句时,username字段为主键,主键是唯一不重复的,但重启服务后会再次创建admin和user,导致数据库报错(在内存数据源上不会出现这种问题,因为重启服务后会清空username字段中的内容)。
所以如果需要在服务启动时便生成部分用户,那么建议先判断用户名是否存在。

    @Beanpublic UserDetailsService userDetailsService(DataSource dataSource){JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager();userDetailsManager.setDataSource(dataSource);//创建用户user01,密码user01,角色userif (!userDetailsManager.userExists("user01")) { //判断user01是否存在userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("user01").password("user01").roles("user").build());}//创建用户admin01,密码admin01,角色adminif (!userDetailsManager.userExists("admin01")) {//判断admin01是否存在userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("admin01").password("admin01").roles("admin").build());}return userDetailsManager;}

补充

WebSecurityConfigurer Adapter定义了三个configure:

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {this.disableLocalConfigureAuthenticationBldr = true;}public void configure(WebSecurity web) throws Exception {}protected void configure(HttpSecurity http) throws Exception {this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();}

我们只用到了一个参数,用来接收 HttpSecurity 对象的配置方法。另外两个参数也有各自的用途,其中,AuthenticationManagerBuilder的configure同样允许我们配置认证用户:

@EnableWebSecurity
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/api/user/**").hasAnyRole("user")  //user 角色访问/api/user/开头的路由.antMatchers("/api/admin/**").hasAnyRole("admin") //admin 角色访问/api/admin/开头的路由.antMatchers("/api/public/**").permitAll()                 //允许所有可以访问/api/public/开头的路由.and().formLogin();}//    @Bean
//    public UserDetailsService userDetailsService(){//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
//
//        //创建用户user01,密码user01,角色user
//        userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("user01").password("user01").roles("user").build());
//        //创建用户admin01,密码admin01,角色admin
//        userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("admin01").password("admin01").roles("admin").build());
//
//        return userDetailsManager;
//    }//    @Bean
//    public UserDetailsService userDetailsService(DataSource dataSource){//        JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager();
//        userDetailsManager.setDataSource(dataSource);
//
//        //创建用户user01,密码user01,角色user
//        if (!userDetailsManager.userExists("user01")) { //判断user01是否存在
//            userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("user01").password("user01").roles("user").build());
//        }
//        //创建用户admin01,密码admin01,角色admin
//        if (!userDetailsManager.userExists("admin01")) {//判断admin01是否存在
//            userDetailsManager.createUser(User.withDefaultPasswordEncoder().username("admin01").password("admin01").roles("admin").build());
//        }
//        return userDetailsManager;
//    }@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("user01")).roles("user").and().passwordEncoder(new BCryptPasswordEncoder()).withUser("admin01").password(new BCryptPasswordEncoder().encode("admin01")).roles("admin");}
}

自定义数据库模型的认证于授权

InMemoryUserDetailsManagerJdbcUserDetailsManager两个类都是UserDetailsService的实现类,自定义数据库结构实际上也仅需实现一个自定义的UserDetailsService
UserDetailsService仅定义了一个loadUserByUsername方法,用于获取一个UserDetails对象。UserDetails对象包含了一系列在验证时会用到的信息,包括用户名、密码、权限以及其他信息,Spring Security 会根据这些信息判定验证是否成功。

public interface UserDetailsService {UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}

也就是说,不管数据库结构如何变化,只要能构造一个UserDetails即可。

自定义实现UserDetail

1.编写实体User实现UserDetail

public class User implements UserDetails {private Long id;private String userName;private String password;private Boolean enable;private String roles;private List<GrantedAuthority> authentications;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public void setPassword(String password) {this.password = password;}public String getRoles() {return roles;}public void setRoles(String roles) {this.roles = roles;}public Boolean getEnable() {return enable;}public void setEnable(Boolean enable) {this.enable = enable;}public List<GrantedAuthority> getAuthentications() {return authentications;}public void setAuthentications(List<GrantedAuthority> authentications) {this.authentications = authentications;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.getAuthentications();}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.userName;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return this.enable;}
}

实现UserDetails定义的几个方法:

  • isAccountNonExpiredisAccountNonLockedisCredentialsNonExpired 暂且用不到,统一返回rue,否则Spring Security会认为账号异常。
  • isEnabled对应enable字段,将其代入即可。
  • getAuthorities方法本身对应的是roles字段,但由于结构不一致,所以此处新建一个,并在后续进行填充。

2.数据库持久层

这里使用JPA 实现实体关系型映射,建立实体与数据库的关系:

(1)需要引入spring-boot-stater-data-jpa依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>

(2)更改User为数据库映射实体类:

@Entity
@Table
public class User implements UserDetails {@Id@GeneratedValue(strategy = GenerationType.SEQUENCE)private Long id;private String userName;private String password;private Boolean enable;private String roles;@Transientprivate List<GrantedAuthority> authentications;........

(3)新建UserRepository

public interface UserRepository extends JpaRepository<User,Long> {User findByUserName(String userName);
}

自定义实现UserDetailsService

@Service
public class MyUserDetailServiceImpl implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//(1)从数据库获取用户User user = userRepository.findByUserName(username);if (user==null)//用户不存在throw new RuntimeException("用户"+username+"不存在!");//(2)将数据库中的roles解析为UserDetail的权限集String roles = user.getRoles();List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(roles);user.setAuthentications(grantedAuthorities);return user;}
}

AuthorityUtils.commaSeparatedStringToAuthorityList(String list) 是spring security 提供的将逗号隔开的权限集字符串切割为权限对象列表,当然上面代码中我们也可以自己实现来代替:

    List<GrantedAuthority> getGrantedAuthorities(String roles){List<GrantedAuthority> grantedAuthorities = new ArrayList<>();String[] split = StringUtils.split(roles, ";");for (int i = 0;i<split.length;i++){if (!StringUtils.isEmpty(split[i])){SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(split[i]);grantedAuthorities.add(grantedAuthority);}}return grantedAuthorities;}

至此,我们就实现了Spring Security的自定义数据库结构认证工程。

(二)基于数据库的认证与授权相关推荐

  1. 5.1基于JWT的认证和授权「深入浅出ASP.NET Core系列」

    原文:5.1基于JWT的认证和授权「深入浅出ASP.NET Core系列」 希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错,希望点个赞,谢 ...

  2. Spring Security 基于数据库的认证

    介绍 之前使用的全是基于内存的认证,这里使用基于数据库的认证. 设计数据表 这里设计数据表 创建项目 这里使用Mybatis作为项目. 添加如下依赖 添加driud连接池依赖 <dependen ...

  3. Spring Boot安全管理—基于数据库的认证

    基于数据库的认证 1. 设计数据库表 首先设计一个基本的用户角色表,一共三张表,分别是用户表.角色表以及用户角色关联表. SET FOREIGN_KEY_CHECKS=0;-- ----------- ...

  4. security基于数据库的认证(二)

    @TOC 前言:在security初体验(一)里,用户名和密码的认证都是基于内存级别的,本章主要是说security如何从db里取用户名和密码来验证的. 1.准备的sql DROP TABLE IF ...

  5. Springboot整合shiro基于url身份认证和授权认证

    你还不会shiro吗? 前奏 shiro核心配置文件(rolesFilter可选). 身份认证 多表登录源如何操作? 授权管理 如何解决界面多角色/资源问题 访问效果 权限管理在日常开发中很重要,所以 ...

  6. mysql url认证_Springboot+shiro基于url身份认证和授权认证

    你还不会shiro吗?前奏 shiro核心配置文件(rolesFilter可选). 身份认证 多表登录源如何操作? 授权管理 如何解决界面多角色/资源问题 访问效果 权限管理在日常开发中很重要,所以硬 ...

  7. 【数据库二】数据库用户管理与授权

    数据库用户管理与授权 1.MySQL数据库管理 1.1 常用的数据类型 1.2 char和varchar区别 1.3 SQL语句分类 2.数据表高级操作 2.1 克隆表 2.2 清空表 2.3 创建临 ...

  8. 【spring-security基础】基于数据库的认证方式

    通过查询数据库方式进行认证 核心要点 实现思路 1. 数据库设计 2. 框架依赖引入(操作数据库) 3. 创建操作数据库相关内容 4. [重点]实现UserDetailsService接口 5. 配置 ...

  9. rabbitmq基于http的认证和授权

    关于用户的登录. user_login_authentication(Username, AuthProps) ->case http_req(p(user_path), q([{usernam ...

最新文章

  1. PAT (Basic Level) Practice (中文)1048 数字加密 (20 分)
  2. 解决ScrollView与ViewPage滑动冲突的问题
  3. windows server 2008 r2之间的ftp传输脚本
  4. MATLAB-1:入门基础
  5. 是人是谁_其实,我们每个人心中都有一把尺子,谁好谁歹谁心里都明白……
  6. icd植入是大手术吗_骨折手术植入了钢板,骨折痊愈后,需要取出钢板吗?
  7. spring boot系列教程2--从helloworld开始
  8. (十二)Linux内核驱动之poll和select
  9. svn拉取文件失败_转自: linux svn命令行无法拉取中文名称的文件
  10. mysql主从复制1064_mysql主从复制或其他操作报错ERROR 1064 (42000): You have an er
  11. SECS I II HSMS 和GEM初步资料总结
  12. python爬取豆瓣图书前250
  13. 【可见光室内定位】(三)基于图像传感器CMOS的可见光室内定位技术
  14. 在线学习Java的资源网站
  15. 解决The number of method references in a .dex file cannot exceed 64K的问题
  16. 从零开始学习UCOSII操作系统13--系统移植理论篇
  17. 云测 Testing 兼职众测平台题目及答案
  18. 项目管理工具DHTMLX Gantt灯箱元素配置教程:配置灯箱元素
  19. 幽默故事:1、有实力的女人;2、小小挖蕨鸡(木子家原创)
  20. 海量数据分布式存储技术-作业五

热门文章

  1. ubuntu16.04下面安装搜狗中文输入法
  2. css overflow属性及使用方法
  3. DW大学生网页作业制作设计 基于html+css我的家乡贵州网页项目的设计与实现
  4. c语言的源程序的后缀名是,C语言源程序文件的后缀名是()。
  5. ip地址转换成字符串
  6. 性能分析-云盘-sysbench IO测速脚本
  7. Git版本回退和撤销修改
  8. Kafka Confluent 简介
  9. PHP检查端口是否可以被绑定
  10. set -x 与 set +x