一、原有权限系统设计
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();
}
}
改进:采用注释的方式
- @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;
})
);
}
}