Spring Security 实现用户登录(MD5+Salt 加密)

1.spring security是什么?

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

2、添加依赖

首先,构建一个简单的Web工程,以用于后续添加安全控制。
本文采用的是Spring Boot + Thymeleaf的框架,使用Maven构建项目,储存用户名和密码的数据库采用的是H2 Database。
Spring Boot的入门和Spring Boot JPA访问H2 Database入门实现我们就不具体阐述了,需要的同学可以参考以下两个链接:

  • Spring Boot快速入门
  • Spring Boot JPA访问H2 Database

废话不多说,当构建完成一个基本的额Spring Boot项目后,首先,我们在pom.xml检查下是否都已经包含了如下配置,引入对Spring Security的依赖。pom.xml文件如下:


4.0.0
com.syf.demo
sprong-security-login
0.0.1-SNAPSHOT


    UTF-8
    1.8



    org.springframework.boot
    spring-boot-starter-parent
    1.5.2.RELEASE



    
        org.springframework.boot
        spring-boot-starter
        
            
                org.springframework.boot
                spring-boot-starter-logging
            
        
    
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-thymeleaf
    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
        javax.persistence
        persistence-api
        1.0.2
    
    
    
        com.h2database
        h2
    



    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
    

3.创建Java 配置类

Spring Security的接入方式一共有两种:基于注解方式和基于xml配置方式。
这里我们采用基于注解的方式,项目采用Spring Boot为基础。
除了最基本的Spring Boot配置之外,我么首先要创建Spring Security的Java 配置类。这里我们创建类SecurityConfiguration继承WebSecurityConfigurerAdapter,来对我们应用中所有的安全相关的事项(所有url,验证用户名密码,表单重定向等)进行控制。

3.1请求拦截策略

Spring security的请求拦截匹配有两种风格,ant风格和正则表达式风格。编码方式是通过重载configure(HttpSecurity)方法实现。

@Configuration
@EnableWebSecurity
@ComponentScan("sample.service")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private TestingUserDetailService userDetailService;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .antMatchers("/css/**", "/fonts/**", "/js/**").permitAll()
      .anyRequest().fullyAuthenticated().and()
      .formLogin().loginPage("/login").failureUrl("/login?error").permitAll().and()
      .logout().permitAll();
    http.csrf().disable();
  }

  ....

}
  • @EnableWebSecurity: 禁用Boot的默认Security配置,配合@Configuration启用自定义配置(需要扩展WebSecurityConfigurerAdapter)
  • configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求。
    (1)通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。例如以上代码指定了访问css,js等静态资源的时候不需要任何认证就可以访问,其他的路径都必须通过身份验证。
    (2)通过formLogin()定义当需要用户登录时候,转到的登录页面。

3.2 密码加密策略

通常我们在存储密码的时候都是进行加密的,spring security默认提供了三种密码存储方式,同时也可以使用自定义的加密方式:
1.NoOpPasswordEncoder 明文方式保存
2.BCtPasswordEncoder 强hash方式加密
3.StandardPasswordEncoder SHA-256方式加密
4.实现PasswordEncoder接口 自定义加密方式
这里我们采用自定义的加密方式在 configure(HttpSecurity http)后面添加 configure(AuthenticationManagerBuilder auth)来实现MD5+Salt的验证方式。

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setPasswordEncoder(passwordEncoder());
    authProvider.setUserDetailsService(userDetailService);
    ReflectionSaltSource saltSource = new ReflectionSaltSource();
    saltSource.setUserPropertyToUse("salt");
    authProvider.setSaltSource(saltSource);
    auth.authenticationProvider(authProvider);
  }
  
  @Bean
  public Md5PasswordEncoder passwordEncoder() {
    return new Md5PasswordEncoder();
  }
  • configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务,这里通过配置身份验证,注入自定义身份验证Bean和密码校验规则(MD5+Salt)。

4.用户存储认证方式

Spring security提供了多种用户存储认证方式:

  1. 使用基于内存的用户存储
  @Override
    public void configure(AuthenticationManagerBuilder auth)throws Exception{
        //基于内存的用户存储、认证
        auth.inMemoryAuthentication()
                .withUser("admin").password("admin").roles("ADMIN","USER")
                .and()
                .withUser("user").password("user").roles("USER");
    }
  1. 基于数据库表用户存储认证
   @Override
    public void configure(AuthenticationManagerBuilder auth)throws Exception{
        //基于数据库的用户存储、认证
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select account,password,true from user where account=?")
                .authoritiesByUsernameQuery("select account,role from user where account=?");
    }
  1. 配置自定义的用户存储认证
    本文采用的就是配置自定义的用户存储认证,自定义的方式也很简单。只需要提供一个UserDetailService接口实现即可。在密码加密策略中用到的userDetailService就是本文自定义的用户存储认证,其代码如下:
@Service
public class TestingUserDetailService implements UserDetailsService {

  private static final Logger logger = LoggerFactory.getLogger(TestingUserDetailService.class);

  @Autowired
  private UserRepository userRepository;
  
  public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    logger.debug("Load user by username: " + userName);
    List authorities = new ArrayList();
    User user = userRepository.findByUserName(userName);
    if (user == null) {
      logger.error("User not found");
      throw new UsernameNotFoundException("User not found");
    }

    authorities.add(new SimpleGrantedAuthority(user.getRole().toString()));
    return new UserWithSalt(user.getUserName(), user.getUserName(), user.getPassword(),
        authorities);
  }

}

自定义的方式只要实现接口方法loadUserByUsername(String username)即可,返回代表用户的UserDetails对象。这里我们返回的是UserWithSalt对象,使用用户名作为“Salt值”来混淆,实现密码的MD5+Salt加密,该对象的具体实现是:

  public UserWithSalt(String username, String salt, String password,
      Collection authorities) {
    super(username, password, authorities);
    this.salt = salt;
  }

User实体类的具体信息如下:

@Entity
@Table(name = "user")
public class User implements Serializable{

  private static final long serialVersionUID = 8831737280677584496L;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @Column(name = "user_name", unique = true, nullable = false)
  private String userName;

  @Column(name = "password", nullable = false)
  private String password;

  @Column(name = "role", nullable = false)
  @Enumerated(EnumType.STRING)
  private Role role;

  @Column(name = "active", nullable = false)
  private boolean active;

  protected User() {}

  /**
   * Constructor.
   * 
   * @param userName String
   * @param password String
   * @param role Role
   * @param active Boolean
   */
  public User(String userName, String password, Role role, boolean active) {
    super();
    this.userName = userName;
    this.password = password;
    this.role = role;
    this.active = active;
  }

  @Override
  public String toString() {
    return String.format("User[id=%d, name='%s', password='%s', role='%s', active='%b']", id,
        userName, password, role, active);
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public Role getRole() {
    return role;
  }

  public void setRole(Role role) {
    this.role = role;
  }

  public boolean isActive() {
    return active;
  }

  public void setActive(boolean active) {
    this.active = active;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

}

5. Web层实现请求映射

这里我提供了两种方法:
1.传统的controller来控制跳转,大部分应用场景中View和后台都会有数据交互,所以采用这种方式比较普遍。
2.通过WebMvcConfigurerAdapter来实现,想通过一个URL Mapping然后不经过Controller处理直接跳转到页面上。

@Controller
public class MainController {

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public String home(Map model) {
    model.put("message", "Hello World");
    model.put("title", "Hello Home");
    model.put("date", new Date());
    return "home";
  }

  @RequestMapping("/foo")
  public String foo() {
    throw new RuntimeException("Expected exception in controller");
  }

}
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
  
  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/login").setViewName("login");
  }
 
}

通过上面的配置,不用添加LoginController或者处理“login”的方法就可以直接通过"/login"访问到login.html页面了!

6.实现映射的页面

src\main\resources\templates\login.html



    
        Login
        
    
    
        

Login with Username and Password

You have been logged out

There was an error, please try again

src\main\resources\templates\home.html



    
    
    
        

Title

Fake content
July 11, 2012 2:17:16 PM CDT

本文通过一个非常简单的示例完成了对Web应用的安全控制,从数据库中读取用户名和密码信息,并利用加密算法提高了安全性,但是Spring Security提供的功能还远不止于此,更多Spring Security的使用可参见Spring Security Reference。
完整示例:
Demo Github 地址

你可能感兴趣的:(Spring Security 实现用户登录(MD5+Salt 加密))