@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and().formLogin().loginPage("/login").failureUrl("/login?error").usernameParameter("username").passwordParameter("password").permitAll() .and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout").permitAll() .and().rememberMe().key("9D119EE5A2B7DAF6B4DC1EF871D0AC3C"); }2.登录页面
<input type="checkbox" name="remember-me" value="true"/>Remember me3.测试
2.如果使用Spring MVC <form:form>标签或Thymeleaf 2.1+,使用@EnableWebMvcSecurity替换@EnableWebSecurity,那么提交的表单会自动嵌入CsrfToken提交到服务端(使用4.0.0.RC1发现@EnableWebMvcSecurity已过时,@EnableWebSecurity已有这样的功能,即用回@EnableWebSecurity即可)
三.增强密码(为密码加点盐).比较简单的方法是使用DaoAuthenticationProvider,这个AuthenticationProvider就可以设置PasswordEncoder.
1.先定义一个PasswordEncoder Bean,使用Md5PasswordEncoder不算强大,通过彩虹表破解就可能危险了,下面是使用BCryptPasswordEncoder.
@Bean public PasswordEncoder passwordEncoder(){ //这里的strength为4-31位,设置成16都觉得编码有点慢了 PasswordEncoder passwordEncoder=new BCryptPasswordEncoder(4); return passwordEncoder; }
关于BCryptPasswordEncoder如何加解密,就看org.springframework.security.crypto.bcrypt.BCrypt的API文档.
BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in "A Future-Adaptable Password Scheme" by Niels Provos and David Mazieres.
This password hashing system tries to thwart off-line password cracking using a computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher. The work factor of the algorithm is parameterised, so it can be increased as computers get faster.
Usage is really simple. To hash a password for the first time, call the hashpw method with a random salt, like this:
String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
To check whether a plaintext password matches one that has been hashed previously, use the checkpw method:
if (BCrypt.checkpw(candidate_password, stored_hash)) System.out.println("It matches"); else System.out.println("It does not match");
The gensalt() method takes an optional parameter (log_rounds) that determines the computational complexity of the hashing:
2.将这个PasswordEncoder和UserDetailsService共同注入到DaoAuthenticationProvider.
@Bean public AuthenticationProvider authenticationProvider(){ //这里使用自带的DaoAuthenticationProvider(如果满足不了需求,就参照此类再自定义) DaoAuthenticationProvider authenticationProvider=new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService()); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; }
或者自己不必创建AuthenticationProvider Bean,通过重写org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder)方法创建AuthenticationProvider(实际spring security内部也是创建DaoAuthenticationProvider)
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); }
3.可以测试登录了.可能数据库表的用户密码的数据不知怎么生成.最简单的是写一个main方法:
public static void main(String[] args) { PasswordEncoder passwordEncoder=new BCryptPasswordEncoder(4); String result=passwordEncoder.encode("123"); System.out.println(result); }
更改过了实体,更改一下org.exam.repository.UserRepositoryImpl#loadUserByUsername.这里是使用自已编写的JPQL来创建查询.
package org.exam.repository; import org.exam.domain.Authority; import org.exam.domain.User; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Created by xin on 15/1/7. */ public class UserRepositoryImpl implements UserRepositoryCustom { public static final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT u FROM User u WHERE u.username=?1"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY ="SELECT a FROM User u INNER JOIN u.authorities a WHERE u.username=?1"; public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY ="SELECT a FROM User u INNER JOIN u.roles r INNER JOIN r.authorities a WHERE u.username=?1"; @PersistenceContext private EntityManager entityManager; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ List<User> users=entityManager.createQuery(DEF_USERS_BY_USERNAME_QUERY, User.class).setParameter(1, username).setMaxResults(1).getResultList(); if (users.size()==0) { throw new UsernameNotFoundException(messages.getMessage("UserRepositoryImpl.notFound",new Object[] {username}, "Username {0} not found")); }else{ Set<Authority> authorities = new HashSet<Authority>(); authorities.addAll(entityManager.createQuery(DEF_AUTHORITIES_BY_USERNAME_QUERY, Authority.class).setParameter(1, username).getResultList()); authorities.addAll(entityManager.createQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY, Authority.class).setParameter(1, username).getResultList()); Set<GrantedAuthority> grantedAuthorities=new HashSet<GrantedAuthority>(authorities.size()); for (Authority authority:authorities){ grantedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority())); } User user=users.get(0); //转为spring security用户和权限,多余信息移除,如果使用session保存认证用户,就可以减小内存占用. return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.isEnabled(), user.isAccountNonExpired(),user.isCredentialsNonExpired(),user.isAccountNonLocked(),grantedAuthorities); } } }四.其它
关于CSRF攻击:
假设银行网站提供一个表单,它允许从当前登录的用户转帐到另一个银行帐户.例如,HTTP请求可能与如下相似:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
现在再假设你认证了你的银行站点,并且没有登出,去访问了一个恶意网站.此恶意网站包含如下一个表单的HTML页面:
<form action="https://bank.example.com/transfer" method="post">
<input type="hidden" name="amount" value="100.00"/>
<input type="hidden" name="routingNumber" value="evilsRoutingNumber"/>
<input type="hidden" name="account" value="evilsAccountNumber"/>
<input type="submit" value="Win Money!"/>
</form>
你想要赢得这部分钱,那么你会点击提交按钮.在这个过程中,你无意转了100美元到一个恶意用户.这是因为,虽然恶意网站无法看到你的cookies,但是此cookies会和你关联的银行伴随着请求一起发送.
更糟的是,整个过程可以自动使用JavaScript.这意味着你甚至不需要点击按钮,那么我们如何保护自己免受这种攻击?(哈哈,当然spring security的防止csrf功能就是一种解决方案).