SpringSecurity的权限认证框架(一)

1、前言

        目前流行的权限校验框架主要ShoriSpringSecury,这篇主要讲解的是在基于前后端分离的基础上对SpringSecurity的使用,前后端使用JWT进行身份鉴定,使用也比较的简单,主要是配置问题,话不多说,上代码。

主要的校验流程:

SpringSecurity的权限认证框架(一)_第1张图片

2、代码展示

2.1 自定义SpringSecurityConfig 配置类

package com.pzg.chat.config;

import com.pzg.chat.filter.JwtAuthenticationTokenFilter;
import com.pzg.chat.handler.AccessDecisionManagerImpl;
import com.pzg.chat.handler.FilterInvocationSecurityMetadataSourceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@EnableWebSecurity
@Configuration
@SuppressWarnings("all")
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


	@Autowired
	private AuthenticationFailureHandler authenticationFailureHandler;

	@Autowired
	private AuthenticationSuccessHandler authenticationSuccessHandler;

	@Autowired
	private AccessDeniedHandler accessDeniedHandler;

	@Autowired
	private AuthenticationEntryPoint authenticationEntryPoint;

	@Autowired
	private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

	@Bean
	public FilterInvocationSecurityMetadataSource securityMetadataSource(){
		return new FilterInvocationSecurityMetadataSourceImpl();
	}

	@Bean()
	public AccessDecisionManager accessDecisionManager(){
		return new AccessDecisionManagerImpl();
	}

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

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
				.loginProcessingUrl("/user/login")   //登入接口路径
				.usernameParameter("account")   //账号
				.passwordParameter("password")//密码
				.failureHandler(authenticationFailureHandler)  //自定义登入验证失败
				.successHandler(authenticationSuccessHandler); //自定义成功


		http.authorizeRequests()
				.withObjectPostProcessor(new ObjectPostProcessor() {
					@Override
					public  O postProcess(O o) {
						o.setSecurityMetadataSource(securityMetadataSource());
						o.setAccessDecisionManager(accessDecisionManager());
						return o;
					}
				})
				.anyRequest().permitAll()  //放行所有
				.and()
				.sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //禁用session
				.and()
				.csrf()     //防止CSRF攻击
				.disable()
				.exceptionHandling()
				//定义权限不足失败
				.accessDeniedHandler(accessDeniedHandler)
				//定义用户未登入
				.authenticationEntryPoint(authenticationEntryPoint)
				//让jwtAuthenticationTokenFilter在UsernamePasswordAuthenticationFilter之前执行
				.and().addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

	}

	/**
	 * 暴露认证方法
	 * @return
	 * @throws Exception
	 */

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

}

   通过去继承WebSecurityConfigurerAdapter类 并重写里面的configure()方法和authenticationManagerBean()方法,重写configure()主要就是去配置自己的规则,而重写authenticationManagerBean()方法则是去暴露自定义校验的方法,源码注释有说明。

属性类说明:

        1.AuthenticationFailureHandler:登入失败处理类

        2.AuthenticationSuccessHandler:登入成功处理类

        3.AccessDeniedHandler:权限不足处理类

        4.AuthenticationEntryPoint:用户未登入处理类

        5.JwtAuthenticationTokenFilter:JWT过滤器

        6.FilterInvocationSecurityMetadataSource:获取访问路径需要的权限

        7.accessDecisionManager:判断所访问路径权限与当前登入用户所拥有权限是否匹配

3、SpringSecurityConfig 配置类所依赖的类

3.1 AuthenticationFailureHandler类

package com.pzg.chat.handler;

import com.alibaba.fastjson.JSON;
import com.pzg.chat.constant.CommonConstant;
import com.pzg.chat.exception.BusinessException;
import com.pzg.chat.model.vo.ResultVO;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录失败处理
 */
@Component
public class AuthenticationFailHandlerImpl implements AuthenticationFailureHandler {


    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        //设置响应字符集utf-8
        httpServletResponse.setContentType(CommonConstant.APPLICATION_JSON);
        if (e.getCause()!=null){
            if (e.getCause() instanceof BusinessException){
                BusinessException businessException = (BusinessException) e.getCause();
                ResultVO resultVO = ResultVO.fail(businessException.getCode(),businessException.getReason());
                httpServletResponse.getWriter().write(JSON.toJSONString(resultVO));
            }else{
                httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.fail(e.getMessage())));
            }
        }else{
            //返回错误信息
            httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.fail(e.getMessage())));
        }
    }

}
 
  

3.2 AuthenticationSuccessHandler类

        当登入成功后,就会触发这个类里的onAuthenticationSuccess方法,可以自己添加相应的逻辑即可。

package com.pzg.chat.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.pzg.chat.constant.CommonConstant;
import com.pzg.chat.entity.UserAuth;
import com.pzg.chat.mapper.UserAuthMapper;
import com.pzg.chat.model.dto.UserDetailsDTO;
import com.pzg.chat.model.dto.UserInfoDTO;
import com.pzg.chat.model.vo.ResultVO;
import com.pzg.chat.service.TokenService;
import com.pzg.chat.utils.BeanCopy;
import com.pzg.chat.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Objects;

@Slf4j
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    @Resource
    private UserAuthMapper userAuthMapper;

    @Resource
    private TokenService tokenService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //将登入成功封装的用户信息复制到UserInfoDTO
        UserInfoDTO userLoginDTO = BeanCopy.singleCopy(UserUtil.getUserDetailsDTO(), UserInfoDTO.class);
       //判断
        if (Objects.nonNull(authentication)) {
            //从authentication.getPrincipal()获取UserDetailsDTO信息,先进行强转
            UserDetailsDTO userDetailsDTO = (UserDetailsDTO) authentication.getPrincipal();
            //生成token,并刷新了用户信息,存入redis
            String token = tokenService.createToken(userDetailsDTO);
            //封装token
            userLoginDTO.setToken(token);
        }
        //设置响应的字符utf-8
        response.setContentType(CommonConstant.APPLICATION_JSON);
        //以流的方式返回
        response.getWriter().write(JSON.toJSONString(ResultVO.ok(userLoginDTO), SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue));
        //最后并刷新用户信息,存入数据库
        updateUserInfo();
    }

    @Async
    public void updateUserInfo() {
        UserAuth userAuth = UserAuth.builder()
                //获取id,通过security提供的SecurityContextHolder获取本线程的用户id
                .id(UserUtil.getUserDetailsDTO().getId())
                //获取访问的地址
                .ipAddress(UserUtil.getUserDetailsDTO().getIpAddress())
                //获取ip来源
                .ipSource(UserUtil.getUserDetailsDTO().getIpSource())
                //获取登入时的时间
                .lastLoginTime(LocalDateTime.now())
                .build();
        //更新数据
        userAuthMapper.updateById(userAuth);
    }
}

3.3 AccessDeniedHandlerImpl类

package com.pzg.chat.handler;


import com.alibaba.fastjson.JSON;
import com.pzg.chat.constant.CommonConstant;
import com.pzg.chat.model.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Slf4j
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        log.error("错误:"+accessDeniedException.getClass());
        response.setContentType(CommonConstant.APPLICATION_JSON);
        response.getWriter().write(JSON.toJSONString(ResultVO.fail("权限不足")));
    }
}

3.4 AuthenticationEntryPointImpl类

package com.pzg.chat.handler;
import com.alibaba.fastjson.JSON;
import com.pzg.chat.constant.CommonConstant;
import com.pzg.chat.model.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
	
		response.setContentType(CommonConstant.APPLICATION_JSON);
		response.getWriter().write(JSON.toJSONString(ResultVO.fail(40001, "用户未登录")));
	}

}

3.5 JwtAuthenticationTokenFilter类

package com.pzg.chat.filter;
import com.pzg.chat.model.dto.UserDetailsDTO;
import com.pzg.chat.service.TokenService;
import com.pzg.chat.utils.UserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

@Configuration
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {


	@Autowired
	private TokenService tokenService;


	@Override
	protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
		UserDetailsDTO userDetailsDTO=null;
		try {
			 userDetailsDTO = tokenService.getUserDetailsDTO(httpServletRequest);
		}catch (Exception ignored){

		}

		//判断userDetailsDTO是否为null,为null说明用户已过期身份需要重修登入,并提交由springSecurity负责响应
		//UserUtil.getAuthentication()判断当前springSecurity上下文应该为null,因为底层使用ThreadLocal,请求一次就会移除
		if (Objects.nonNull(userDetailsDTO) && Objects.isNull(UserUtil.getAuthentication())){
			//刷新token
			tokenService.renewToken(userDetailsDTO);
			UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetailsDTO, null, userDetailsDTO.getAuthorities());
			SecurityContextHolder.getContext().setAuthentication(authenticationToken);
		}
		filterChain.doFilter(httpServletRequest,httpServletResponse);
	}
}

注意,这里的 tokenService.getUserDetailsDTO(httpServletRequest);是从自己封装的一个类,主要是从每次请求的头部获取token,并通过解析token后,将保存在redis的用户信息获取出来。然后通过SecurityContextHolder.getContext().setAuthentication(authenticationToken);将用户信息保存到SpringSecurity上下文中

3.6 FilterInvocationSecurityMetadataSource类

package com.pzg.chat.handler;
import com.pzg.chat.mapper.RoleMapper;
import com.pzg.chat.model.dto.ResourceListDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

@Slf4j
@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {

	@Autowired
	private RoleMapper roleMapper;

	private static List resourceLists;

	@PostConstruct
	public void init(){
		resourceLists = roleMapper.resourceInit();
	}

	@Override
	public Collection getAttributes(Object object) throws IllegalArgumentException {
		if (Objects.isNull(resourceLists)){
			init();
		}
		FilterInvocation filterInvocation = (FilterInvocation) object;
		//访问的方法
		String method = filterInvocation.getRequest().getMethod();
		//访问的路径
		String url = filterInvocation.getRequest().getRequestURI();

		AntPathMatcher antPathMatcher = new AntPathMatcher();

		for (ResourceListDTO resourceList : resourceLists) {
			if (antPathMatcher.match(url,resourceList.getUrl()) && method.equalsIgnoreCase(resourceList.getRequestMethod())){
				List roleList = resourceList.getRoleList();
				if (CollectionUtils.isEmpty(roleList)){
					return SecurityConfig.createList("disable");
				}else{
					return SecurityConfig.createList(roleList.toArray(new String[]{}));
				}
			}
		}
		//返回null表示匿名访问
		return null;
	}

	@Override
	public Collection getAllConfigAttributes() {
		return null;
	}

	@Override
	public boolean supports(Class clazz) {
		return FilterInvocation.class.isAssignableFrom(clazz);
	}
}

       其中 resourceLists = roleMapper.resourceInit();是当容器启动后就将数据库的每个路径对应的权限信息加载进来,方便根据用户请求的路径及请求方法来找到所需要的权限

3.7 accessDecisionManager类

package com.pzg.chat.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {
	@Override
	public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

		//获取用户封装的权限信息
		List authentications = authentication
				.getAuthorities()
				.stream()
				.map(GrantedAuthority::getAuthority)
				.collect(Collectors.toList());
		for (ConfigAttribute configAttribute : configAttributes) {
			if (authentications.contains(configAttribute.getAttribute())){
				return;
			}
		}
		if (SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals("anonymousUser")){
			throw new DisabledException("用户未登入");
		}else{
			throw new AccessDeniedException("权限不足");
		}
	}

	@Override
	public boolean supports(ConfigAttribute attribute) {
		return false;
	}

	@Override
	public boolean supports(Class clazz) {
		return false;
	}
}

4、 自定义登入校验规则

        这里就是整个SpringSecurity最重要的部分了,在源码中可以发现,SpringSecurity通过将密码层层传递后,最后会到达一个名为loadUserByUsername()的方法里,而这个方法就是UserDetailsService接口下的一个方法,如果实现并重写loadUserByUsername()方法,则SpringSecurity就会从内存中去校验账号密码,也就是为什么在含有springsecurity的项目启动时,控制台会有一串密码,这个密码就是springsecurity生成的一个临时密码,并保存在内存当中,话不多说,看代码实现:

4.1 UserDetailsServiceImpl类

package com.pzg.chat.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pzg.chat.entity.UserAuth;
import com.pzg.chat.entity.UserInfo;
import com.pzg.chat.exception.BusinessException;
import com.pzg.chat.mapper.RoleMapper;
import com.pzg.chat.mapper.UserAuthMapper;
import com.pzg.chat.mapper.UserInfoMapper;
import com.pzg.chat.model.dto.UserDetailsDTO;
import com.pzg.chat.service.RedisService;
import com.pzg.chat.utils.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;

import static com.pzg.chat.constant.CommonConstant.*;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Resource
	private RoleMapper roleMapper;

	@Resource
	private UserInfoMapper userInfoMapper;

	@Resource
	private UserAuthMapper userAuthMapper;

	@Autowired
	private RedisService redisService;

	@Autowired
	private HttpServletRequest request;

	@Override
	public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
		UserAuth userAuth = userAuthMapper.selectOne(new LambdaQueryWrapper()
						.eq(UserAuth::getUsername, account).or()
						.eq(UserAuth::getEmail,account).or()
				        .eq(UserAuth::getPhone,account));
		if (Objects.isNull(userAuth)){
			throw new BusinessException("输入的账号不存在");
		}
		Object isPass = redisService.get(SLIDER_PASS_ACCOUNTLOGIN + account);
		if (Objects.isNull(isPass) || Integer.parseInt(isPass.toString())!=1){
			throw new BusinessException("校验未通过,请重新进行校验");
		}
		redisService.del(SLIDER_PASS_ACCOUNTLOGIN + account);
		return convertUserDetail(userAuth,request);
	}

	public UserDetailsDTO convertUserDetail(UserAuth userAuth,HttpServletRequest request){
		//TODO 获取用户信息
		UserInfo userInfo = userInfoMapper.selectById(userAuth.getUserInfoId());
		//TODO 获取用户权限
		List roles = roleMapper.selectRoleList(userAuth.getUserInfoId());
		String ipAddress = IpUtil.getIpAddress(request);

		String ipSource = IpUtil.getIpSource(ipAddress);

		return UserDetailsDTO.builder()
				.username(userAuth.getUsername())
				.password(userAuth.getPassword())
				.email(userAuth.getEmail())
				.phone(userAuth.getPhone())
				.avatar(userInfo.getAvatar())
				.disable(userInfo.getDisable())
				.id(userInfo.getId())
				.ipAddress(ipAddress)
				.ipSource(ipSource)
				.lastLoginTime(userAuth.getLastLoginTime())
				.nickName(userInfo.getNickName())
				.roles(roles)
				.gender(userInfo.getGender())
				.build();
	}
}

        这里通过账户去数据库中查找并返回用户信息,因为需要返回UserDetails 类型,所以我们就要去实现这个类,从而自定自己的规则:

4.2 UserDetailsDTO类

package com.pzg.chat.model.dto;

import com.pzg.chat.constant.CommonConstant;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.beans.Transient;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDetailsDTO implements UserDetails {
	//id
	private Integer id;

	//昵称
	private String nickName;

	//账号
	private String username;

	//邮箱
	private String email;

	//手机号
	private String phone;

	//密码
	private String password;

	//头像
	private String avatar;

	//性别
	private Integer gender;

	//ip
	private String ipAddress;

	//位置
	private String ipSource;

	//是否禁用(0禁用,1不禁用)
	private Integer disable;

	//角色
	private List roles;

	//序列化不然报错
	@JsonDeserialize(using = LocalDateTimeDeserializer.class)
	@JsonSerialize(using = LocalDateTimeSerializer.class)
	private LocalDateTime expireTime;

	//序列化不然报错
	@JsonDeserialize(using = LocalDateTimeDeserializer.class)
	@JsonSerialize(using = LocalDateTimeSerializer.class)
	private LocalDateTime lastLoginTime;


	@Override
	@Transient
	public Collection getAuthorities() {
		return roles.stream()
				.map(SimpleGrantedAuthority::new)
				.collect(Collectors.toList());
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return username;
	}

	@Override
	@Transient
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	@Transient
	public boolean isAccountNonLocked() {
		return disable.equals(CommonConstant.Locked);
	}

	@Override
	@Transient
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	@Transient
	public boolean isEnabled() {
		return true;
	}
}

我们只需要重写UserDetails 里的一些方法,并添加自己需要的属性,即可完成整改校验流程

说明:

        对密码进行校验的是SpringSecurity本身帮我们去校验密码正确性了,不用我们去考虑,我们要考虑的仅仅是检验用户是否存在,并将用户信息进行封装即可。

5、总结

        SpringSecurity难度可以说是比较难上手,但是上手后会发现没这么难,并且可制定的东西也非常多,我什么这种只是其中一种写法,难度属于中上一点,还有比较简单的方法,我就不做演示了,还是那句话,坚持总是有意想不到的结果。

        最后说一下,后面我会出分析SpringSecurity源码的文章,讲解SpringSecurity从账号密码发出到校验的整个的过程,共大家相互学习,共同进步。

你可能感兴趣的:(java,开发语言)