Spring Security 단일 프로젝트 권한 설계 프로 세 스 분석

왜 SpringSecurity 를 선 택 했 습 니까?
현재 자바 웹 의 세계 에서 스프링 은 하나의 강호 라 고 할 수 있다.마이크로 서비스 가 도래 함 에 따라 스프링 클 라 우 드 는 자바 프로그래머 가 반드시 익 혀 야 할 프레임 워 크 라 고 할 수 있다.알 리 마저 스프링 클 라 우 드 를 위해 기원 을 썼 다.예 를 들 어 유명한 Nacos)는 Spring 의 친아들 로 서 SpringSecurity 는 마이크로 서비스의 생태 에 잘 적응 했다.당신 은 Oauth 와 매우 간편 하 게 결합 하여 인증 센터 서 비 스 를 할 수 있 습 니 다.본 고 는 가장 간단 한 단일 프로젝트 부터 시작 하여 Security 를 점차적으로 파악 하고 자 한다.공식 문서
준비 하 다.
나 는 간단 한 demo 를 준비 했다.구체 적 인 코드 는 문 말 에 넣 을 것 이다.미리 말씀 드 리 지만 본 demo 는 JWT 를 사용 하지 않 았 습 니 다.왜냐하면 저 는 token 의 유 지 를 서버 에 두 고 기한 이 지난 시간 을 더 잘 유지 하고 싶 기 때 문 입 니 다.(물론 앞으로 마이크로 서비스 인증 센터 형식 이 라면 JWT 도 만 료 기간 을 편리 하 게 유지 하고 지나치게 논의 하지 않 을 수 있 습 니 다)Security+JWT 간이 입문 에 대해 알 고 싶다 면 도장 을 찍 어 주 십시오.
본 항목 의 구 조 는 다음 과 같다.

한편,본 데 모 는 MybatisPlus,lombok 을 사용 했다.
핵심 코드
먼저 두 가지 유형 을 실현 해 야 한다.하 나 는 User Details 의 실현 유형 인 Security User 이 고 하 나 는 User Details Service 의 실현 유형 인 Security User Service 이다.

**
 * Security        User 
 * */
@Data
public class SecurityUser implements UserDetails {
 @Autowired
 private SysRoleService sysRoleService;
 //     (     username SysUser loginName    )
 private String username;
 //    
 private String password;
 //  id
 private SysUser sysUser;
 //        
 private List<SysMenu> sysMenuList;
 /**    */
 public SecurityUser(SysUser sysUser){
  this.username = sysUser.getLoginName();
  this.password = sysUser.getPassword();
  this.sysUser = sysUser;
 }
 public SecurityUser(SysUser sysUser,List<SysMenu> sysMenuList){
  this.username = sysUser.getLoginName();
  this.password = sysUser.getPassword();
  this.sysMenuList = sysMenuList;
  this.sysUser = sysUser;
 }
 /**       */
 @Override
 public Collection<? extends GrantedAuthority> getAuthorities() {
  List<GrantedAuthority> authorities = new ArrayList<>();
  for(SysMenu menu : sysMenuList) {
   authorities.add(new SimpleGrantedAuthority(menu.getPerms()));
  }
  return authorities;
 }
 @Override
 public String getPassword() {
  return this.password;
 }
 @Override
 public String getUsername() {
  return this.username;
 }
 //       
 @Override
 public boolean isAccountNonExpired() {
  return true;
 }
 //        
 @Override
 public boolean isAccountNonLocked() {
  return true;
 }
 //        
 @Override
 public boolean isCredentialsNonExpired() {
  return true;
 }
 //      
 @Override
 public boolean isEnabled() {
  return true;
 }
}
이 종 류 는 어떤 요청 자의 정 보 를 포함 하고 있 으 며,Security 에 서 는 주체 라 고 부른다.그 중에서 이 방법 은 반드시 실현 되 어야 하 며 사용자 의 구체 적 인 권한 을 얻 을 수 있다.우리 쪽 권한 의 과립 도 는 메뉴 단계 에 이 르 렀 습 니 다.많은 오픈 소스 항목 에서 캐릭터 의 등급 이 아니 라 과립 도가 가 늘 수록 편리 하 다 고 생각 합 니 다.(개인 적 으로...)

/**
 * Security        UserService 
 * */
@Service
public class SecurityUserService implements UserDetailsService{

 @Autowired
 private SysUserService sysUserService;
 @Autowired
 private SysMenuService sysMenuService;
 @Autowired
 private HttpServletRequest httpServletRequest;

 @Override
 public SecurityUser loadUserByUsername(String loginName) throws UsernameNotFoundException {
  LambdaQueryWrapper<SysUser> condition = Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLoginName, loginName);
  SysUser sysUser = sysUserService.getOne(condition);
  if (Objects.isNull(sysUser)){
   throw new UsernameNotFoundException("      !");
  }
  Long projectId = null;
  try{
   projectId = Long.parseLong(httpServletRequest.getHeader("projectId"));
  }catch (Exception e){

  }
  SysMenuModel sysMenuModel;
  if (sysUser.getUserType()){
   sysMenuModel = new SysMenuModel();
  }else {
   sysMenuModel = new SysMenuModel().setUserId(sysUser.getId());
  }
  sysMenuModel.setProjectId(projectId);
  List<SysMenu> menuList = sysMenuService.getList(sysMenuModel);
  return new SecurityUser(sysUser,menuList);
 }
}
이 종 류 는 유일한 방법 인 loadUserByUsername 을 실현 하여 특정한 사용자 의 모든 권한 을 얻 고 주 체 를 생 성 할 수 있 으 며 뒤의 filter 에서 그의 역할 을 볼 수 있 습 니 다.
설정 과 filter 를 보기 전에 한 가지 설명 이 필요 합 니 다.이러한 제공 방법 은 사용자 가 로그 인하 지 않 거나 token 이 실 효 된 상황 에서 통일 적 으로 되 돌아 갈 수 있 습 니 다.

@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

 private static final long serialVersionUID = 1L;

 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response,
       AuthenticationException authException) throws IOException, ServletException {
  response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token  ,      ");
 }
}
ok.다음은 설정 을 보고 웹 보안 Configurer Adapter 의 Security Config 류 를 실현 합 니 다.특히 이 demo 는 앞 뒤 가 분 리 된 전제 에서 쓴 것 이기 때문에 너무 많은 방법 을 실현 할 수 있 습 니 다.사실은 이 종 류 는 세 가지 방법 을 실현 할 수 있 습 니 다.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

 @Autowired
 SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint;
 @Autowired
 SecurityFilter securityFilter;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http
    //  csrf
    .csrf().disable()
    //    
    .exceptionHandling().authenticationEntryPoint(securityAuthenticationEntryPoint).and()
    //Session    
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    //    
    .authorizeRequests()
    .antMatchers("/login/login").permitAll()
    .antMatchers("/login/register").permitAll()
    .antMatchers("/login/logout").permitAll()
    .anyRequest().authenticated();
  http
    .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class);
 }
}
이상 처 리 는 바로 위의 클래스 입 니 다.Session 의 몇 가지 관리 방식 은 제 가 그 Security+JWT 의 글 에서 도 설명 을 했 습 니 다.비교적 간단 합 니 다.그 다음 에 검증 되 지 않 은 로그 인 경 로 를 몇 개 하고 나머지 는 저희 아래 의 filter 를 거 쳐 야 합 니 다.

@Slf4j
@Component
public class SecurityFilter extends OncePerRequestFilter {

 @Autowired
 SecurityUserService securityUserService;
 @Autowired
 SysUserService sysUserService;
 @Autowired
 SysUserTokenService sysUserTokenService;

 /**
  *     
  * */
 @Override
 protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
         FilterChain filterChain) throws ServletException, IOException {
  log.info("      :{}",httpServletRequest.getRequestURL());
  try {
   final String token = httpServletRequest.getHeader("token");
   LambdaQueryWrapper<SysUserToken> condition = Wrappers.<SysUserToken>lambdaQuery().eq(SysUserToken::getToken, token);
   SysUserToken sysUserToken = sysUserTokenService.getOne(condition);
   if (Objects.nonNull(sysUserToken)){
    SysUser sysUser = sysUserService.getById(sysUserToken.getUserId());
    if (Objects.nonNull(sysUser)){
     SecurityUser securityUser = securityUserService.loadUserByUsername(sysUser.getLoginName());
     //       
     UsernamePasswordAuthenticationToken authentication =
       new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
     authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
     //      
     SecurityContextHolder.getContext().setAuthentication(authentication);
    }
   }
  }catch (Exception e){
   log.error("       :{}", Arrays.toString(e.getStackTrace()));
  }
  filterChain.doFilter(httpServletRequest, httpServletResponse);
 }
}
사용자 의 로그 인 여 부 를 판단 하 는 것 은 데이터베이스 에서 만 료 되 지 않 은 token 이 있 는 지 확인 하 는 것 입 니 다.존재 하면 주체 정 보 를 프로젝트 의 메모리 에 넣 습 니 다.특히 모든 요청 체인 이 끝나 면 Security ContextHolder.getContext()의 데 이 터 는 clear 되 기 때문에 요청 할 때마다 set 가 필요 합 니 다.
이상 은 Security 핵심 생 성 을 완 료 했 습 니 다.업무 코드 가 메모리 의 주체 정 보 를 쉽게 얻 기 위해 저 는 사용자 정 보 를 얻 는 방법 을 특별히 추 가 했 습 니 다.

/**
 *   Security     
 * @author pjjlt
 * */
public class SecurityUserUtil {
 public static SysUser getCurrentUser(){
  SecurityUser securityUser = (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  if (Objects.nonNull(securityUser) && Objects.nonNull(securityUser.getSysUser())){
   return securityUser.getSysUser();
  }
  return null;
 }
}
비 즈 니스 코드
이상 은 Security 핵심 코드 입 니 다.로그 인 과 특정한 인터페이스의 권한 접근 테스트 등 두 개의 업무 코드 를 간단하게 추가 합 니 다.
만물 의 근원 등록 등극
우선,filter 에 의 해 차단 되 지 않 는 세 가지 방법 으로 등록,로그 인,로그아웃 을 했 습 니 다.저 는 모두 moudle.contrller.Login Controller 라 는 경로 에 썼 습 니 다.등록 은 말 할 필요 도 없 이 insert User 의 방법 입 니 다.판단 을 잘 하면 됩 니 다.비밀 번 호 는 AES 를 통 해 비밀 번 호 를 추가 합 니 다.
로그 인 코드 를 보 니 contrller 층 은 말 하지 않 겠 습 니 다.어차피 검사 입 니 다.

 /**
  *   ,      ,      
  * */
 @Override
 @Transactional(rollbackFor = Exception.class)
 public JSONObject login(SysUserModel sysUserModel) throws Exception{
  JSONObject result = new JSONObject();
  //1.         、      、      
  Wrapper<SysUser> sysUserWrapper = Wrappers.<SysUser>lambdaQuery()
    .eq(SysUser::getLoginName,sysUserModel.getLoginName()).or()
    .eq(SysUser::getEmail,sysUserModel.getEmail());
  SysUser sysUser = baseMapper.selectOne(sysUserWrapper);
  if (Objects.isNull(sysUser)){
   throw new Exception("     !");
  }
  String password = CipherUtil.encryptByAES(sysUserModel.getPassword());
  if (!password.equals(sysUser.getPassword())){
   throw new Exception("     !");
  }
  if (sysUser.getStatus()){
   throw new Exception("         !");
  }
  // 2.        
  sysUser.setLoginIp(ServletUtil.getClientIP(request));
  sysUser.setLoginDate(LocalDateTime.now());
  baseMapper.updateById(sysUser);
  // 3.  token,    
  String token = UUID.fastUUID().toString().replace("-","");
  LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireTimeSeconds);
  SysUserToken sysUserToken = new SysUserToken()
    .setToken(token).setUserId(sysUser.getId()).setExpireTime(expireTime);
  sysUserTokenService.save(sysUserToken);
  result.putOpt("token",token);
  result.putOpt("expireTime",expireTime);
  return result;
 }
먼저 사용자 가 존재 하 는 지,로그 인 비밀번호 가 정확 한 지 확인 한 다음 에 token 을 패키지 합 니 다.특히 저 는 데이터베이스(sys UserToken)에서 사용자 가 로그 인 한 token 을 가 져 오지 않 았 습 니 다.그리고 만 료 된 시간 형식 을 업데이트 하여 로그 인 을 하 는 것 이 아니 라 로그 인 할 때마다 새로운 token 을 얻 었 습 니 다.그러면 여러 단 계 를 로그 인 할 수 있 습 니 다.후기 에는 계 정 로그 인 수량 에 대한 통제 도 할 수 있다.
그리고 라 이브 러 리 에 존재 하 는 token 을 삭제 합 니 다.

 /**
  *   ,  token
  * */
 @Override
 public void logout() throws Exception{
  String token = httpServletRequest.getHeader("token");
  if (Objects.isNull(token)){
   throw new LoginException("token   ",ResultEnum.LOGOUT_ERROR);
  }
  LambdaQueryWrapper<SysUserToken> sysUserWrapper = Wrappers.<SysUserToken>lambdaQuery()
    .eq(SysUserToken::getToken,token);
  baseMapper.delete(sysUserWrapper);
 }
권한 검증
여기 서 저 는 두 개의 계 정 을 지 켰 습 니 다.하 나 는 슈퍼 관리자 majian 이 고 모든 권한 을 가지 고 있 습 니 다.하 나 는 일반인pjjlt,권한 만 있 습 니 다.인터페이스 에 접근 하 는 효 과 를 보 겠 습 니 다.
우리 가 방문 한 인 터 페 이 스 는 moudle.controller.LoginController 경로 아래 에 있 습 니 다.

@PreAuthorize("hasAnyAuthority('test')")
@GetMapping("test")
public String test(){
 return "test";
}
그 중에서 hasAny Authority('test')는 권한 코드 입 니 다.
서로 다른 계 정 으로 접근 하 는 것 을 모 의 하 는 것 은 헤더 의 token 값 을 바 꾸 는 것 입 니 다.바로 로그 인 단계 에서 전단 의 token 으로 돌아 가 는 것 입 니 다.
우선 슈퍼 관리자 인증 입 니 다.

그리고 일반 관리자 방문.

이어서 로그 인 하지 않 았 습 니 다(token 이 존재 하지 않 거나 만 료 되 었 습 니 다).

demo 주소
https://github.com/majian1994/easy-file-back
종결 어
본 고 는 간단하게 설명 했다.주로 Security 와 관련 된 것 을 구체 적 으로 캐릭터 를 실현 하 는 세 가지 요소 이다.사용자,역할,권한(메뉴)은 제 코드 를 볼 수 있 고 테스트 를 마 쳤 습 니 다.문서 관리 시스템 을 써 서 저희 회사 가 인터페이스 문 서 를 잘 관리 하도록 도와 주 려 고 했 는데 한 파트너 가 좋 은 오픈 소스 를 찾 았 습 니 다.그래서 이 코드 는 나의 작은 demo 가 되 었 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기