04基于JDBC的认证

前面章节的数据并不是完全来自于数据库,比如spring security用户初始只是在xml配置,后来改为数据库了,再有权限一直都是根据工具类虚拟出来的.下面的例子我们继续改进.本文是在chapter04.00-calendar基础上修改而来.


一.基于组的访问控制
JdbcUserDetailsManager支持增加一个在用户与GrantedAuthority的声明之间的间接层,通过分组GrantedAuthority成为被叫做组的逻辑集。
然后用户被分配一个或多个组,其成员赋予一组的GrantedAuthority声明

1.配置好数据源.
表定义与初始化数据

a.security-schema.sql

create table users(
    username varchar(256) not null primary key,
    password varchar(256) not null,
    enabled boolean not null
);

create table authorities (
    username varchar(256) not null,
    authority varchar(256) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
b.security-users.sql

insert into users (username,password,enabled) values ('user1@example.com','user1',1);
insert into users (username,password,enabled) values ('admin1@example.com','admin1',1);
insert into users (username,password,enabled) values ('user2@example.com','admin1',1);
insert into users (username,password,enabled) values ('disabled1@example.com','disabled1',0);
c.security-groups-schema.sql

create table groups (
    id bigint generated by default as identity(start with 0) primary key,
    group_name varchar(256) not null
);

create table group_authorities (
    group_id bigint not null,
    authority varchar(50) not null,
    constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);

create table group_members (
    id bigint generated by default as identity(start with 0) primary key,
    username varchar(50) not null,
    group_id bigint not null,
    constraint fk_group_members_group foreign key(group_id) references groups(id)
);
d.security-groups-mappings.sql
-----
-- Create the Groups
insert into groups(group_name) values ('Users');
insert into groups(group_name) values ('Administrators');

-----
-- Map the Groups to Roles
insert into group_authorities(group_id, authority) select id,'ROLE_USER' from groups where group_name='Users';
-- Administrators are both a ROLE_USER and ROLE_ADMIN
insert into group_authorities(group_id, authority) select id,'ROLE_USER' from groups where group_name='Administrators';
insert into group_authorities(group_id, authority) select id,'ROLE_ADMIN' from groups where group_name='Administrators';

-----
-- Map the users to Groups
insert into group_members(group_id, username) select id,'user1@example.com' from groups where group_name='Users';
insert into group_members(group_id, username) select id,'admin1@example.com' from groups where group_name='Administrators';
insert into group_members(group_id, username) select id,'user2@example.com' from groups where group_name='Users';
insert into group_members(group_id, username) select id,'disabled1@example.com' from groups where group_name='Users';
service.xml
<jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:/database/h2/calendar-schema.sql"/>
        <jdbc:script location="classpath:/database/h2/calendar-data.sql"/>
        <jdbc:script location="classpath:/database/h2/security-schema.sql"/>
        <jdbc:script location="classpath:/database/h2/security-users.sql"/>
        <jdbc:script location="classpath:/database/h2/security-groups-schema.sql"/>
        <jdbc:script location="classpath:/database/h2/security-groups-mappings.sql"/>
    </jdbc:embedded-database>
2.配置JdbcUserDetailsManager使用组
默认情况下,Spring Security没有启用GBAC。因此,我们必须指出Spring security启用组的使用.
修改security.xml文件中使用group-authorities-by-username-query,如下所示:
<authentication-manager>
  <authentication-provider>
	<jdbc-user-service id="userDetailsService" data-source-ref="dataSource"
	 group-authorities-by-username-query=
		"select
		g.id, g.group_name, ga.authority
		from
		groups g, group_members gm, group_authorities ga
		where
		gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"/>
  </authentication-provider>
</authentication-manager>

3.注册用户时调用

userDetailsManager.createUser(userDetails);

设置SecurityContext调用

UserDetails userDetails = userDetailsService.loadUserByUsername(user.getEmail());
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                user.getPassword(),userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);

4.启动jetty测试

二.支持自定义方案.很多数据库并不符合spring security的数据表定义,但用以过JdbcDaoImpl来映射.
JdbcUserDetailsManager有三条明确的定义参数和返回列的sql查询.
命名空间查询的属性名称 描述期望SQL列
users-by-username-query:返回一个或多个匹配用户名的用户;但只有第一用户被使用.Username (string),Password (string),Enabled (Boolean)
authorities-by-username-query:返回一个或多个直接授予用户的权限;通常用于GBAC被禁用的情况下.Username (string),Granted Authority (string)
group-authorities-by-username-query:返回通过组成员关系的用户的权限和组详细.当GBAC启用时使用.Group Primary Key (any),Group Name (any),Granted Authority (string)
返回列不一定要使用JdbcUserDetailsManager的默认实现,但这些列无论如何都要返回的.


1.再回到之前的例子,我们将所有以security-开头的sql都移掉,加入calendar-authorities.sql,如下:

<jdbc:embedded-database id="dataSource" type="H2">
	<jdbc:script location="classpath:/database/h2/calendar-schema.sql"/>
	<jdbc:script location="classpath:/database/h2/calendar-data.sql"/>
	<jdbc:script location="classpath:/database/h2/calendar-authorities.sql"/>
</jdbc:embedded-database>
2.DefaultCalendarService.java
    private final JdbcOperations jdbcOperations;
    @Autowired
    public DefaultCalendarService(EventDao eventDao, CalendarUserDao userDao, JdbcOperations jdbcOperations) {
    ...
    this.jdbcOperations = jdbcOperations;
    }

    public int createUser(CalendarUser user) {
        int userId = userDao.createUser(user);
        jdbcOperations.update("insert into calendar_user_authorities(calendar_user,authority) values (?,?)",
        userId, "ROLE_USER");
        return userId;
    }
3.配置JdbcUserDetailsManager使用自定义sql查询.
        <authentication-provider>
            <jdbc-user-service id="userDetailsService" data-source-ref="dataSource"
                users-by-username-query="select email,password,true
                from calendar_users
                where email = ?"
                authorities-by-username-query="select cua.id, cua.authority
                from calendar_users cu, calendar_user_authorities cua
                where cu.email = ? and  cu.id = cua.calendar_user"/>
        </authentication-provider>
4.启动jetty测试

三.配置安全密码.我们知道,经过加密的密码会更安全.spring security为我们实现了不少编码器,都放在o.s.s.authentication.encoding包下面.
1.声明一个编码器bean

<bean:bean id="passwordEncoder"
       xmlns="http://www.springframework.org/schema/beans"
       class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
	<constructor-arg value="256"/>
</bean:bean>
2.让spring security知道passwordEncoder.在 <authentication-provider>的子元素加入<password-encoder ref="passwordEncoder"/>
3.有了这两步,如果使用前面的应用,去注册一个用户是能自动登录,但去查h2数据库,发现密码并没有改变,以前的用户也没法登录上去,
退出当前新注册的用户一样不能登录上去.原因很简单,我没保存的时候,用户的密码并没有经过密码编码器编码.所以更新一下

DefaultCalendarService,注入passwordEncoder(发现这本书的作者很喜欢用构造器的方法注入bean),然后在createUser方法使用user.setPassword(passwordEncoder.encodePassword(user.getPassword(),null));然后发现新注册的
用户就可以登录了,当然,初始化的那些用户update一下经过编码的密码一样也可以登录.

四.为密码再加点盐.前面的例子,如果用户的密码相同,那么最终保存在数据库的密码数据也是一样的.如果能做到保存不一样,那就更安全了.
将明文密码与适当的盐共同进行加密生成的密文就可以不同.一般的盐无非就两种:
a.使用与用户相关的数据按算法来生成,如用户创建的时间;b.随机生成的,并且与用户的密码一起按照某种形式进行存储
因为盐加入到了明文密码,所以是不能使用单向加密的.因为对于一个指定的用户,
应用需要得到合适的盐值来计算密码的哈希与用户所存储的哈希进行比较来完成认证.
spring security使用盐.spring security3.1的spring-security-core和spring-security-crypto都提供了o.s.s.crypto.password.PasswordEncoder
应优先使用这个接口的方法,因为使用了随机盐.这接口用三实现:
o.s.s.crypto.bcrypt.BCryptPasswordEncoder:防止穷举搜索攻击。
o.s.s.crypto.password.NoOpPasswordEncoder:以明文形式返回密码
o.s.s.crypto.password.StandardPasswordEncoder:使用SHA-256多次迭代和随机盐值
1.更新passwordEncoder如下:
<bean:bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>
2.password-encoder引用不变
3.DefaultCalendarService里面的passwordEncoder注入的是o.s.s.crypto.password.PasswordEncoder类型而不是之前的
o.s.s.authentication.encoding.PasswordEncoder;createUser方法调用使用user.setPassword(passwordEncoder.encode(user.getPassword()));
4.重启jetty测试


再看看这个例子是如何保存密码与认证的:
a.存储密码:先创建一个随机盐,然后将随机盐和原始密码进行哈希,最后将盐与哈希组合存储
salt = randomsalt()
hash = hash(salt+originalPassword)
storedPassword = salt + hash
b.认证:从存储密码得到盐与哈希(这不能是单向加密).然后将得到的盐与输入的密码进行哈希,这两哈希如果相等就通过认证.
storedPassword = datasource.lookupPassword(username)
salt, expectedHash = extractSaltAndHash(storedPassword)
actualHash = hash(salt+inputedPassword)
authenticated = (expectedHash == actualHash)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值