Spring Security内置了三种用户存储方式:
1、基于内存
2、基于数据库查询语句
3、自定义UserDetailsService服务来获取
这里的用户存储指的是,从哪里获取用户的信息
1、基于内存存储用户信息
在WebSecurityConfig类中配置:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.inMemoryAuthentication() .withUser("user").password("{noop}123456").roles("USER") .and() .withUser("admin").password("{noop}123456").roles("USER","ADMIN"); }
withUser()方法返回的是UserDetailsManagerConfigurer.UserDetailsBuilder,这个对象提供了多个进一步配置用户的方法,如上面的为用户设置密码的password()方法、授予用户多个角色权限的roles()方法。UserDetailsManagerConfigurer.UserDetailsBuilder对象的更多方法如下表:
roles()方法是authorities()方法的简写形式。roles()方法所给定的值都会添加一个“ROLE_”前缀,并将其作为权限授予给用户。如下的配置和上面的等价:
auth.inMemoryAuthentication() .withUser("user").password("{noop}password").authorities("ROLE_USER") .and() .withUser("admin").password("{noop}password").authorities("ROLE_USER","ROLE_ADMIN");
2.基于数据库库sql查询语句
auth .jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("select username,password,true from Spitter where username=?") .authoritiesByUsernameQuery("select username,'ROLE_USER' from Spitter where username=?");
3、自定义UserDetailsService服务来获取用户信息
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(MyUserDetailsService()); }
UserDetailsService在身份认证中的作用
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
在Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀"ROLE_",而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。
所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等等。
GrantedAuthority接口的默认实现SimpleGrantedAuthority
注意,在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示"角色"的权限,在数据库中就带有"ROLE_"前缀了。所以authorities表中的视图可能是这样的。
角色和权限能否分开存储?角色能不能不带"ROLE_"前缀
当然可以分开存储,你可以定义两张表,一张存角色,一张存权限。但是你自定义UserDetailsService的时候,需要保证把这两张表的数据都取出来,放到UserDails的权限集合中。当然你数据库中存储的角色也可以不带"ROLE_"前缀,就像这样。
但是前面说到了,Security才不管你是角色,还是权限。它只比对字符串。
比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串"ROLE_ADMIN"。如果你从角色表中取出用户所拥有的角色时不加上"ROLE_"前缀,那验证的时候就匹配不上了。
所以角色信息存储的时候可以没有"ROLE_"前缀,但是包装成GrantedAuthority对象的时候必须要有。
// 设置添加用户信息,正常应该从数据库中读取
@Bean UserDetailsService userDetailsService() { //内存保存 /* InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); userDetailsService.createUser(User.withUsername("user1").password("{noop}123456") .authorities("ROLE_USER").build()); userDetailsService.createUser(User.withUsername("user2").password("{noop}1234567") .authorities("ROLE_USER").build());*/ //数据库加载 UserDetailsService userDetailsService = new UserDetailsService(){ //根据username加载用户基本信息,权限信息,角色信息 校验可以在自定义的daoAuhthenticationProvider中做 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AuthUser authUser = authUserMapper.selectByUsername(username); if (authUser == null) { throw new UsernameNotFoundException("账户不存在"); } // MyUserDetails myUserDetails = new MyUserDetails(authUser) ; //校验 这里只加载用户信息,尽量不要做校验 校验可以在自定义的daoAuhthenticationProvider中做 /* if (myUserDetails==null || myUserDetails.getAuthorities().isEmpty()) { throw new UsernameNotFoundException("用户未分配任何权限"); }*/ return new User(authUser.getUsername(),authUser.getPassword(),this.getAuthorities(authUser)); } public Collection<GrantedAuthority> getAuthorities(AuthUser authUser) { Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); //查用户角色 List<AuthRole> authRoles = authRoleMapper.selectByUserId(authUser.getId()); //查用户权限 List<AuthPermission> authPermissions = authPermissionMapper.selectByRoles(authRoles); if (authRoles != null) { //角色权限 Set<String> roleStrs = authRoles.stream().map(AuthRole::getRoleName).collect(Collectors.toSet()); for (String code : roleStrs) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code); authorities.add(authority); } //方法权限 Set<String> authStrs = authPermissions.stream().map(AuthPermission::getCode).collect(Collectors.toSet()); for (String code : authStrs) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code); authorities.add(authority); } } return authorities; } }; return userDetailsService; }
说明:
这里的返回结果User就是UserDetails接口的实现类:
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); }
构造方案非常简单:
username 用户名
password 密码
authorities 权限
总结:
1、本章介绍了OAuth2.0用户信息可以有2种存储方式,一种是存放在内存中,一般只会在测试时使用;一种是基于数据库的持久化存储。
三种加载用户信息的方式:
直接基于内存中的用户信息去进行认证inMemoryAuthentication
采用sql语句从数据库加载用户信息
继承UserDetailsService方法,加载用户信息,推荐使用这种方法,最灵活通用。
github源码: