欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security單項(xiàng)目權(quán)限設(shè)計(jì)過程解析

 更新時(shí)間:2019年11月11日 11:14:19   作者:啤酒就辣條  
這篇文章主要介紹了Spring Security單項(xiàng)目權(quán)限設(shè)計(jì)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

為什么選擇SpringSecurity?

現(xiàn)如今,在JavaWeb的世界里Spring可以說是一統(tǒng)江湖,隨著微服務(wù)的到來,SpringCloud可以說是Java程序員必須熟悉的框架,就連阿里都為SpringCloud寫開源呢。(比如大名鼎鼎的Nacos)作為Spring的親兒子,SpringSecurity很好的適應(yīng)了了微服務(wù)的生態(tài)。你可以非常簡便的結(jié)合Oauth做認(rèn)證中心服務(wù)。本文先從最簡單的單體項(xiàng)目開始,逐步掌握Security。更多可達(dá)官方文檔

準(zhǔn)備

我準(zhǔn)備了一個(gè)簡單的demo,具體代碼會放到文末。提前聲明,本demo沒有用JWT,因?yàn)槲蚁氚裻oken的維護(hù)放到服務(wù)端,更好的維護(hù)過期時(shí)間。(當(dāng)然,如果將來微服務(wù)認(rèn)證中心的形式,JWT也可以做到方便的維護(hù)過期時(shí)間,不做過多討論)如果想了解Security+JWT簡易入門,請戳

本項(xiàng)目結(jié)構(gòu)如下

另外,本demo使用了MybatisPlus、lombok。

核心代碼

首先需要實(shí)現(xiàn)兩個(gè)類,一個(gè)是UserDetails的實(shí)現(xiàn)類SecurityUser,一個(gè)是UserDetailsService的實(shí)現(xiàn)類SecurityUserService。

**
 * Security 要求需要實(shí)現(xiàn)的User類
 * */
@Data
public class SecurityUser implements UserDetails {
 @Autowired
 private SysRoleService sysRoleService;
 //用戶登錄名(注意此處的username和SysUser的loginName是一個(gè)值)
 private String username;
 //登錄密碼
 private String password;
 //用戶id
 private SysUser sysUser;
 //該用戶的所有權(quán)限
 private List<SysMenu> sysMenuList;
 /**構(gòu)造函數(shù)*/
 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;
 }
 /**需要實(shí)現(xiàn)的方法*/
 @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;
 }
 //默認(rèn)賬戶未過期
 @Override
 public boolean isAccountNonExpired() {
  return true;
 }
 //默認(rèn)賬戶沒有帶鎖
 @Override
 public boolean isAccountNonLocked() {
  return true;
 }
 //默認(rèn)憑證沒有過期
 @Override
 public boolean isCredentialsNonExpired() {
  return true;
 }
 //默認(rèn)賬戶可用
 @Override
 public boolean isEnabled() {
  return true;
 }
}

這個(gè)類包含著某個(gè)請求者的信息,在Security中叫做主體。其中這個(gè)方法是必須實(shí)現(xiàn)的,可以獲取用戶的具體權(quán)限。我們這邊權(quán)限的顆粒度達(dá)到了菜單級別,而不是很多開源項(xiàng)目中角色那級別,我覺得顆粒度越細(xì)越方便(個(gè)人覺得...)

/**
 * Security 要求需要實(shí)現(xiàn)的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);
 }
}

顯而易見,這個(gè)類實(shí)現(xiàn)了唯一的方法loadUserByUsername,從而可以拿到某用戶的所有權(quán)限,并生成主體,在后面的filter中就可以見到他的作用了。

在看配置和filter之前,還有一個(gè)類需要說明一下,此類提供方法,可以讓用戶未登錄、或者token失效的情況下進(jìn)行統(tǒng)一返回。

@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,接下來看配置,實(shí)現(xiàn)了WebSecurityConfigurerAdapter的SecurityConfig類,特別說明,本demo算是前后端分離的前提下寫的,所以實(shí)現(xiàn)過多的方法,其實(shí)這個(gè)類可以實(shí)現(xiàn)三個(gè)方法。

@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()
    //開啟認(rèn)證
    .authorizeRequests()
    .antMatchers("/login/login").permitAll()
    .antMatchers("/login/register").permitAll()
    .antMatchers("/login/logout").permitAll()
    .anyRequest().authenticated();
  http
    .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

異常處理就是上面那個(gè)類,Session那幾種管理方式我在那篇Security+JWT的文章中也有所講解,比較簡單,然后是幾個(gè)不用驗(yàn)證的登錄路徑,剩下的都需要經(jīng)過我們下面這個(gè)filter。

@Slf4j
@Component
public class SecurityFilter extends OncePerRequestFilter {

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

 /**
  * 認(rèn)證授權(quán)
  * */
 @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());
     //將主體放入內(nèi)存
     UsernamePasswordAuthenticationToken authentication =
       new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
     authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
     //放入內(nèi)存中去
     SecurityContextHolder.getContext().setAuthentication(authentication);
    }
   }
  }catch (Exception e){
   log.error("認(rèn)證授權(quán)時(shí)出錯(cuò):{}", Arrays.toString(e.getStackTrace()));
  }
  filterChain.doFilter(httpServletRequest, httpServletResponse);
 }
}

判斷用戶是否登錄,就是從數(shù)據(jù)庫中查看是否有未過期的token,如果存在,就把主體信息放進(jìn)到項(xiàng)目的內(nèi)存中去,特別說明的是,每個(gè)請求鏈結(jié)束,SecurityContextHolder.getContext()的數(shù)據(jù)都會被clear的,所以,每次請求的時(shí)候都需要set。

以上就完成了Security核心的創(chuàng)建,為了業(yè)務(wù)代碼方便獲取內(nèi)存中的主體信息,我特意加了一個(gè)獲取用戶信息的方法

/**
 * 獲取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;
 }
}

業(yè)務(wù)代碼

以上是Security核心代碼,下面簡單加兩個(gè)業(yè)務(wù)代碼,比如登錄和某個(gè)接口的權(quán)限訪問測試。

萬物之源登錄登出

首先,不被filter攔截的那三個(gè)方法注冊、登錄、登出,我都寫在了moudle.controller.LoginController這個(gè)路徑下,注冊就不用說了,就是一個(gè)insertUser的方法,做好判斷就好,密碼通過AES加個(gè)密。

下面看下登錄代碼,controller層就不說了,反正就是個(gè)驗(yàn)參。

 /**
  * 登錄,返回登錄信息,前端需要緩存
  * */
 @Override
 @Transactional(rollbackFor = Exception.class)
 public JSONObject login(SysUserModel sysUserModel) throws Exception{
  JSONObject result = new JSONObject();
  //1. 驗(yàn)證賬號是否存在、密碼是否正確、賬號是否停用
  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.更新最后登錄時(shí)間
  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;
 }

首先驗(yàn)證下用戶是否存在,登錄密碼是否正確,然后封裝token,值得一提的是,我并沒有從數(shù)據(jù)庫(sysUserToken)中獲取用戶已經(jīng)登錄的token,然后更新過期時(shí)間的形式做登錄,而是每次登錄都獲取新token,這樣就可以做到多端登錄了,后期還可以做賬號登錄數(shù)量的控制。

然后就是登出,刪除庫中存在的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);
 }

權(quán)限驗(yàn)證

這邊我維護(hù)了兩個(gè)賬號,一個(gè)是超級管理員majian,擁有所有權(quán)限。一個(gè)是普通人員_pjjlt,只有一些權(quán)限,我們看一下訪問接口的效果。

我們訪問的接口是moudle.controller.LoginController路徑下的

@PreAuthorize("hasAnyAuthority('test')")
@GetMapping("test")
public String test(){
 return "test";
}

其中hasAnyAuthority('test')就是權(quán)限碼

我們模擬用不同賬號訪問,就是改變請求header中的token值,就是登錄階段返回給前端的token。

首先是超級管理員驗(yàn)證

然后是普通管理員訪問

接著沒有登錄(token不存在或者已過期)訪問

demo地址

https://github.com/majian1994/easy-file-back

結(jié)束語

本文簡單講解了,主要是將Security相關(guān)的東西,具體實(shí)現(xiàn)角色的三要素,用戶、角色、權(quán)限(菜單)可以看我的代碼,都寫完測完了,本來想寫個(gè)文檔管理系統(tǒng),幫助我司更好的管理接口文檔,but有位小伙伴找了一個(gè)不錯(cuò)的開源的了,所以這代碼就成了我的一個(gè)小demo。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java中instanceof關(guān)鍵字實(shí)例講解

    Java中instanceof關(guān)鍵字實(shí)例講解

    大家好,本篇文章主要講的是Java中instanceof關(guān)鍵字實(shí)例講解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • 如何解決TCP?socket的阻塞問題

    如何解決TCP?socket的阻塞問題

    這篇文章主要介紹了如何解決TCP?socket的阻塞問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Mybatis插件擴(kuò)展及與Spring整合原理分析

    Mybatis插件擴(kuò)展及與Spring整合原理分析

    這篇文章主要介紹了Mybatis插件擴(kuò)展及與Spring整合原理,本文通過實(shí)例文字相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java并發(fā)編程之對象的組合

    Java并發(fā)編程之對象的組合

    這篇文章主要介紹了Java并發(fā)編程之對象的組合,文章基于Java的相關(guān)資料展開主題內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-04-04
  • SpringMVC中處理Ajax請求的示例

    SpringMVC中處理Ajax請求的示例

    本篇文章給大家介紹SpringMVC中處理Ajax請求的示例,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • spring中的特殊注解@RequiredArgsConstructor詳解

    spring中的特殊注解@RequiredArgsConstructor詳解

    這篇文章主要介紹了spring中的特殊注解@RequiredArgsConstructor,包括注解注入,構(gòu)造器注入及setter注入,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • 深入解析Java編程中final關(guān)鍵字的使用

    深入解析Java編程中final關(guān)鍵字的使用

    這篇文章主要介紹了Java編程中final關(guān)鍵字的使用,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2016-01-01
  • Java比較兩個(gè)對象是否相等的方法

    Java比較兩個(gè)對象是否相等的方法

    這篇文章主要介紹了Java比較兩個(gè)對象是否相等的方法,文中給出了三種方法,并通過代碼講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-03-03
  • java修飾類的使用方法以及使用技巧(分享)

    java修飾類的使用方法以及使用技巧(分享)

    下面小編就為大家?guī)硪黄猨ava修飾類的使用方法以及使用技巧(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • Java常用線程池原理及使用方法解析

    Java常用線程池原理及使用方法解析

    這篇文章主要介紹了Java常用線程池原理及使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07

最新評論