Spring Security是基于Spring生态圈的,用于提供安全访问解决方案的框架,主要有两大功能:可定制的身份验证和访问控制框架,主要有如下功能:

  1. Spring Security是Spring框架的一个模块,提供了全面的安全解决方案,包括身份认证、授权、攻击防护等功能。
  2. Spring Security构建在Spring框架之上,可以与Spring应用程序无缝集成,利用Spring的依赖注入和AOP等特性。
  3. Spring Security提供了大量的扩展点和配置选项,可以根据需求进行定制和扩展,例如使用自定义的身份验证提供程序、授权策略等。
  4. Spring Security在大型和复杂的应用程序中表现良好,特别是与其他Spring模块集成时。

以下是一种应用的实现方式:

引用pom

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

启动类添加启用注解,启用Spring Security

@EnableGlobalMethodSecurity(prePostEnabled = true)

实现userDetailsService接口

package com.huq.moms.service.impl;


import com.huq.moms.dao.PermissionDao;
import com.huq.moms.dto.LoginUser;
import com.huq.moms.model.SysPermission;
import com.huq.moms.model.SysUser;
import com.huq.moms.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * spring security登陆处理<br>
 */

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserService userService;
	@Autowired
	private PermissionDao permissionDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		SysUser sysUser = userService.getUser(username);
		if (sysUser == null) {
			throw new AuthenticationCredentialsNotFoundException("用户名不存在");
		} else if (sysUser.getStatus() == SysUser.Status.LOCKED) {
			throw new LockedException("用户被锁定,请联系管理员");
		} else if (sysUser.getStatus() == SysUser.Status.DISABLED) {
			throw new DisabledException("用户已作废");
		}

		LoginUser loginUser = new LoginUser();
		BeanUtils.copyProperties(sysUser, loginUser);
		//用户登录权限相关信息放入 loginUser
		List<SysPermission> permissions = permissionDao.listByUserId(sysUser.getId());
		loginUser.setPermissions(permissions);

		return loginUser;
	}

}

loginUser 实现需要集成UserDetails接口

package com.huq.moms.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.huq.moms.model.SysPermission;
import com.huq.moms.model.SysUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
public class LoginUser extends SysUser implements UserDetails {

	private static final long serialVersionUID = -1379274258881257107L;

	private List<SysPermission> permissions;
	private String token;
	/** 登陆时间戳(毫秒) */
	private Long loginTime;
	/** 过期时间戳 */
	private Long expireTime;

	//权限数据存储,用于匹配用户是否有权限
	@Override
	@JsonIgnore
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return permissions.parallelStream().filter(p -> !StringUtils.isEmpty(p.getPermission()))
				.map(p -> new SimpleGrantedAuthority(p.getPermission())).collect(Collectors.toSet());
	}

	public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
		// do nothing
	}

	// 账户是否未过期
	@JsonIgnore
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	// 账户是否未锁定
	@JsonIgnore
	@Override
	public boolean isAccountNonLocked() {
		return getStatus() != Status.LOCKED;
	}

	// 密码是否未过期
	@JsonIgnore
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	// 账户是否激活
	@JsonIgnore
	@Override
	public boolean isEnabled() {
		return true;
	}

	public Long getLoginTime() {
		return loginTime;
	}

	public void setLoginTime(Long loginTime) {
		this.loginTime = loginTime;
	}

	public Long getExpireTime() {
		return expireTime;
	}

	public void setExpireTime(Long expireTime) {
		this.expireTime = expireTime;
	}

}

继承WebSecurityConfigurerAdapter,配置规则相关

package com.huq.moms.security;

import com.huq.moms.security.authentication.MyAuthenctiationFailureHandler;
import com.huq.moms.security.authentication.MyAuthenticationSuccessHandler;
import com.huq.moms.security.authentication.MyLogoutSuccessHandler;
import com.huq.moms.security.authentication.RestAuthenticationAccessDeniedHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
	// 登录成功handler
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
	// 登录失败handler
    @Autowired
    private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
	// 资源禁止访问handler
    @Autowired
    private RestAuthenticationAccessDeniedHandler restAuthenticationAccessDeniedHandler;

    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.authorizeRequests()
                .antMatchers("/login.html",
                        "/my/**",
                        "/treetable-lay/**",
                        "/xadmin/**",
                        "/ztree/**",
                        "/statics/**"
                        )
                .permitAll()
                .anyRequest()
                .authenticated()
                ;
        //解决X-Frame-Options DENY问题
        httpSecurity.headers().frameOptions().sameOrigin();
        httpSecurity.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenctiationFailureHandler)
        .and().logout().permitAll().invalidateHttpSession(true).
                deleteCookies("JSESSIONID").logoutSuccessHandler(myLogoutSuccessHandler)
        ;
        //异常处理
        httpSecurity.exceptionHandling().accessDeniedHandler(restAuthenticationAccessDeniedHandler);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

}

常用认证注解

  1. @Secured :方法执行前检查,直接判断有没有对应的角色
  2. @PreAuthorize:方法执行前检查,根据SpEL表达式执行结果判断是否授权
  3. @PostAuthorize:方法执行后检查,根据SpEL表达式执行结果判断是否授权

示例(sys:menu:del对应LoginUser方法中getAuthorities()中集合权限数据,在权限矩阵中配置):

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    @ResponseBody
    @PreAuthorize("hasAuthority('sys:menu:del')")
    @ApiOperation(value = "删除菜单", notes = "根据菜单Id去删除菜单")//描述
    public Results deletePermission(SysPermission sysPermission) {
        return permissionService.delete(sysPermission.getId());
    }

Spring Security认证步骤

  1. 自定UserDetails类:当实体对象字段不淌足时需要自定义UserDetails,一般都要自定义UserDetails。
  2. 自定义UserDetailsService类,主要用于从数据库查询用户信息。
  3. 创建登录认证成功处理器,认证成功后需要返回JSON数据,菜单权限等。
  4. 创建登录认证失败处理器,认证失败需要返回JSON数据,给前端判断。
  5. 创建匿名用户访问无权限资源时处理器,匿名用户访问时.需要提示JSON.
  6. 创建认证过的用户访问无权限资源时的处理器,无权限访问时,需要提示JSON。
  7. 配置Spring Security配置类,把上面自定义的处理器交给Spring Security.