天天看點

Jhipster權限控制設計及改進二、權限改進設計

一、原有權限系統設計

Jhipster權限控制設計及改進二、權限改進設計

1.後端API通路控制

在SecurityConfiguartion.java的configure方法中配置

@Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
        .and()
            .headers()
            .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
        .and()
            .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
        .and()
            .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
        .and()
            .frameOptions()
            .deny()
        .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/api/authenticate").permitAll()
            .antMatchers("/api/register").permitAll()
            .antMatchers("/api/activate").permitAll()
            .antMatchers("/api/account/reset-password/init").permitAll()
            .antMatchers("/api/account/reset-password/finish").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/management/health").permitAll()
            .antMatchers("/management/info").permitAll()
            .antMatchers("/management/prometheus").permitAll()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
        .and()
            .httpBasic()
        .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    }
           

URL比對權限有三種:

  • permitAll :允許任何人
  • authenticated :允許認證的使用者通路
  • hasAuthority : 允許指定權限的使用者通路

2.後端URL動詞通路控制

在Restfull接口檔案中,以UserResource.java為例,添加PreAuthorize注釋

/**
     * {@code DELETE /users/:login} : delete the "login" User.
     *
     * @param login the login of the user to delete.
     * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
     */
    @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
    @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
    public ResponseEntity<Void> deleteUser(@PathVariable String login) {
        log.debug("REST request to delete User: {}", login);
        userService.deleteUser(login);
        return ResponseEntity.noContent().headers(HeaderUtil.createAlert(applicationName,  "userManagement.deleted", login)).build();
    }

           

3.後端Entity通路控制

通過 SecurityUtils擷取登入使用者資訊,隻能通路有特定資訊的entity

@GetMapping("/bank-accounts/{id}")
@Timed
public ResponseEntity<BankAccountDTO> getBankAccount(@PathVariable Long id) {
    log.debug("REST request to get BankAccount : {}", id);
    Optional<BankAccountDTO> bankAccountDTO = bankAccountRepository.findById(id)
        .map(bankAccountMapper::toDto);

    // Return 404 if the entity is not owned by the connected user
    Optional<String> userLogin = SecurityUtils.getCurrentUserLogin();
    if (bankAccountDTO.isPresent() &&
        userLogin.isPresent() &&
        userLogin.get().equals(bankAccountDTO.get().getUserLogin())) {
        return ResponseUtil.wrapOrNotFound(bankAccountDTO);
    } else {
        return ResponseEntity.notFound().build();
    }
}
           

改進:采用注釋的方式

  1. @PostAuthorize (“returnObject.type == authentication.name”)

DomainUserDetailsService

SecurityUtils:

isCurrentUserInRole

isAuthenticated

getCurrentUserLogin

4.前端module通路控制

Angular每個module都有modulename.route.ts檔案,包含路由資訊

export const bankAccountPopupRoute: Routes = [
    {
        path: 'bank-account/:id/delete',
        component: BankAccountDeletePopupComponent,
        resolve: {
            bankAccount: BankAccountResolve
        },
        data: {
            authorities: ['ROLE_USER'],
            pageTitle: 'jhipsterSampleApplicationApp.bankAccount.home.title'
        },
        canActivate: [UserRouteAccessService],
        outlet: 'popup'
    }
];
           

具體實作由user-route-access-service.ts的

canActivate調用checkLogin實作權限的控制目的

5.前端界面元素通路控制

使用jhiHasAnyAuthority屬性

<button *jhiHasAnyAuthority="'ROLE_ADMIN'"
        type="submit"
        [routerLink]="['/', { outlets: { popup: 'bank-account/'+ bankAccount.id + '/delete'} }]"
        replaceUrl="true"
        queryParamsHandling="merge"
        class="btn btn-danger btn-sm">
    <fa-icon [icon]="'times'"></fa-icon>
    <span class="d-none d-md-inline" jhiTranslate="entity.action.delete">Delete</span>
</button>
           

其實質是通過has-any-authority.directive.ts的updateView函數,調用this.accountService.hasAnyAuthority實作

6.前端ts通路控制

this.accountService.hasAnyAuthority(‘ROLE_ADMIN’)

二、權限改進設計

1.後端權限控制設計

a、定制權限通路控制服務,通過PreAuthorize控制通路

服務

@Service
public class SecurityService {
  
   public boolean hasAccess(int parameter) {
       return parameter == 1;
   }
}
           

通路控制

@PreAuthorize("@securityService.hasAccess(#id)")
Message findOne(Long id);
           

b、Secured Filter攔截

通過繼承常用的FilterSecurityInterceptor方式實作

2.前端權限控制設計改進

a、定制權限通路控制服務,通過PreAuthorize控制通路

标題

@Injectable({ providedIn: 'root' })
export class UserRouteAccessService implements CanActivate {
  constructor(
    private router: Router,
    private loginModalService: LoginModalService,
    private accountService: AccountService,
    private stateStorageService: StateStorageService
  ) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    const authorities = route.data['authorities'];
    // We need to call the checkLogin / and so the accountService.identity() function, to ensure,
    // that the client has a principal too, if they already logged in by the server.
    // This could happen on a page refresh.
    return this.checkLogin(authorities, state.url);
  }

  checkLogin(authorities: string[], url: string): Observable<boolean> {
    return this.accountService.identity().pipe(
      map(account => {
        if (!authorities || authorities.length === 0) {
          return true;
        }

        if (account) {
          this.accountService.authenticate(account);
          const hasAnyAuthority = this.accountService.hasAnyAuthority(authorities);
          if (hasAnyAuthority) {
            return true;
          }
          if (isDevMode()) {
            console.error('User has not any of required authorities: ', authorities);
          }
          this.router.navigate(['accessdenied']);
          return false;
        }

        this.stateStorageService.storeUrl(url);
        this.router.navigate(['']);
        this.loginModalService.open();
        return false;
      })
    );
  }
}