参考的Remember-Me Authentication和Cross Site Request Forgery (CSRF)一.加入remember me,下面是基于Token,还有一种基于持久化.1.修改配置@Overrideprotected 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.测试二.启用CSRF.一旦启用,那些Action为PATCH, POST, PUT, and DELETE的请求(包含登录和登出)都要附加CSRF Token提交到服务端.还有,登出也要使用POST(参考官方文档,当然可改为GET,但不推荐).1.下面使用比较笨的方法:加入csrf Input标签.a.在登录页面和其它表单都添加加入<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>b.登出:<form action="${logoutUrl}" method="post"><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/><input type="submit" value="退出"/></form>
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.
@Beanpublic PasswordEncoder passwordEncoder(){//这里的strength为4-31位,设置成16都觉得编码有点慢了PasswordEncoder passwordEncoder=new BCryptPasswordEncoder(4);return passwordEncoder;}
2.将这个PasswordEncoder和UserDetailsService共同注入到DaoAuthenticationProvider.
@Beanpublic 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)
@Overrideprotected 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";@PersistenceContextprivate EntityManager entityManager;@Override@Transactionalpublic 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);}}}四.其它${pageContext.request.remoteUser}这样可以获取当前登录的用户名.jsp页面要根据权限来显示页面元素,可以先引入spring-security-taglibs包,再使用spring security标签就可以.
,轻轻的风,吹开你紧锁的眉头,