Springboot+Spring secuirty 前后端分離后台菜單權限設計


背景:菜單和權限在系統中是非常重要的事情,在結合自己研究過的Spring security和項目前后端實踐中對進行總結。

介紹:使用基於RBAC權限模型,針對角色分配不同的權限

數據庫設計:

系統菜單                                                系統角色                                              菜單角色表

 

                              

  用戶對應的角色                               用戶信息

                 

技術:Spring security+jjwt

Spring security:是Spring 開源的權限管理框架,由一組過濾器鏈組成,對不同的訪問進去攔截和控制,也可以自己實現權限攔截

spring security 的核心功能主要包括:

  • 認證 (你是誰)
  • 授權 (你能干什么)
  • 攻擊防護 (防止偽造身份)

jjwt:是一個提供端到端的JWT創建和驗證的Java庫,可以生成加密的token,並可以從token反推出存放在token的一些信息(如用戶賬號)——參考官網https://jwt.io/introduction/

 

實現:通過UserDetailsService 和UserDetails 通過數據庫獲取用戶信息如(權限,用戶賬號)

步驟一:

// 定義jjwt的用戶的一些信息,在后面生成token時需要,並且Spring security要獲取實現UserDetails 接口用戶信息  
@Getter @AllArgsConstructor
public class SystemUser implements UserDetails { @JSONField(serialize = false) private final Long id; private final String username; @JSONField(serialize = false) private final String password; public Long getId() { return id; } private final String salt;
// 權限 @JSONField(serialize
= false) private final Collection<GrantedAuthority> authorities; @JSONField(serialize = false) @Override public boolean isAccountNonExpired() { return true; } @JSONField(serialize = false) @Override public boolean isAccountNonLocked() { return true; } @JSONField(serialize = false) @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return false; } @JSONField(serialize = false) @Override public String getPassword() { return password; } public Collection getRoles() { return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); } }

 

步驟二:實現 UserDetailsService 接口,這里我使用mybatis查詢數據庫,通過用戶賬號獲取數據庫用戶信息

public class SystemUserDetailsService implements UserDetailsService {

    @Autowired
    private ISysUserService userService;

    @Autowired
    private JwtPermissionService permissionService; // 獲取用戶角色的菜單權限

    @Override
    public UserDetails loadUserByUsername(String username) {
        SysUser user = userService.findByName(username);
        if (user == null) {
            throw new ServiceException("賬號不存在");
        } else {
            if (user.getUserStatus().equals(Constants.OrganizationStatus.DISABLE)) {
                throw new ServiceException("賬號已被禁用");
            }
            return createJwtUser(user);
        }
    }

    public UserDetails createJwtUser(SysUser user) {
        return new SystemUser(
                user.getId(),
                user.getUsername(),
                user.getPassword(),
                user.getSalt(),
                permissionService.mapToGrantedAuthorities(user),
                user.getCreateTime()
        );
    }
}

  

步驟三:JwtPermissionService 實現,請注意這是實現的虛假邏輯,具體的還要看業務邏輯

@Component
public class JwtPermissionService{

@Autowired
private IUsersRolesService usersRolesService;
@Autowired
private IRolesMenuService rolesMenuService;

public Collection<GrantedAuthority> mapToGrantedAuthorities(SysUser user){
// step 1 根據用戶賬號獲取用戶的角色
Set<Role> menu =usersRolesService.getRole(String userName);
// step 2 根據角色獲取用戶的菜單
Set<Menu>menuList=rolesMenuService.getMenu(Set<Role>role);
 // step 3 獲取菜單對應的menu_make 進行轉換

return menuVos.stream().filter(x -> !StringUtils.isEmpty(x.getMenuMark())).map(result -> {

String permission =result.getMenuMark();
return new SimpleGrantedAuthority(permission);}
).collect(Collectors.toList());
}
}

 

 步驟4:定義Spring security  權限配置

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

@Autowired
private JwtTokenFilter tokenFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtUserDetailsService)
.passwordEncoder(passwordEncoderBean());
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去掉前綴
return new GrantedAuthorityDefaults("");
}

// 加密方式
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}// 權限攔截規則,千萬不要.login() 這直接走表單驗證了,會比較麻煩
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {

httpSecurity

// 禁用 CSRF
.csrf().disable()
// 不創建會話
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 過濾請求
.authorizeRequests()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).anonymous()

.antMatchers( HttpMethod.POST,"/auth/login).permitAll()
.antMatchers("/websocket/**").anonymous()
// 所有請求都需要認證
.anyRequest().authenticated()
// 防止iframe 造成跨域
.and().headers().frameOptions().disable();
// 添加自定義攔截器
httpSecurity
.addFilterBefore(
tokenFilter,UsernamePasswordAuthenticationFilter.class);
    }}

 

 步驟4:自定義攔截器,通過此攔截器, 前端訪問時候頭部要帶"Authorization",通過token獲取用戶信息

@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
 
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String requestHeader = request.getHeader("Authorization");
String authToken=null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
String userName =Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken ).getBody().getSubject();

}

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
SystemUser userDetails = (SystemUser ) this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
}

 

步驟五:登錄返回toekn 給前端

@Getter
@AllArgsConstructor
public class AuthenticationInfo implements Serializable {

private final String token;

private final JwtUser user;
}

// 登錄構造器

@RequestMapping("auth") public class SecurityController{
@Autowired
@Qualifier("SystemUserDetailsService")
private UserDetailsService userDetailsService;
@PostMapping(value = "${jwt.auth.path}")
public AuthorizationUser login(@RequestParam("userName")String userName,@RequestParam("password")String password)) {
    final SystemUser jwtUser = (SystemUser ) userDetailsService.loadUserByUsername(userName);
 //獲取用戶的token,是否存在
Date expirationDate = new Date(createdDate.getTime() +864000);
    String token =Jwts.builder()
.setClaims(claims)
.setSubject()
.setIssuedAt(new Date)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();

return new AuthenticationInfo(token, jwtUser));
}

}

 

步驟6 定義具有某個菜單的構造器,前端通過定義菜單標識跟后台@PreAuthorize 對應的權限進行關聯起來,這樣就可以形成對應的權限

@RequestMapping("/admin")
public class Demo {

@RequestMapping("/pageList")
@PreAuthorize("hasAnyRole(‘menu_mark’)")
public List<String> pageList(){
return new ArrayList();
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM