内容简介:use spring-security protect ResourceServer
來補一下怎麼保護 ResourceServer
套件依賴
build.gradle
buildscript { ext { springBootVersion = '1.5.2.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' jar { baseName = 'ps-account' version = '0.0.1-SNAPSHOT' } sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-data-rest') compile('org.springframework.boot:spring-boot-starter-mail') compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.boot:spring-boot-starter-web') compile 'org.springframework.security.oauth:spring-security-oauth2:2.0.12.RELEASE' compile 'org.springframework.security:spring-security-jwt:1.0.7.RELEASE' compile 'org.modelmapper:modelmapper:0.7.7' compile 'org.apache.commons:commons-lang3:3.5' compile 'mysql:mysql-connector-java:6.0.5' compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.1' compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.1' compile group: 'io.springfox', name: 'springfox-data-rest', version: '2.6.1' compileOnly('org.projectlombok:lombok') testCompile('org.springframework.boot:spring-boot-starter-test') }
spring-security-oauth2 spring-security-jw 是我們需另外加進來的
我們會有一些基礎的帳號或是角色的物件
帳號
Account.java
@Data @Entity @EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效 public class Account { @Id //@GeneratedValue private String accountid; @NotNull @UniqueUsername(message = "Username already exists") @Size(min = 5, max = 255, message = "Username have to be grater than 8 characters") @Column(unique = true) private String username; private String email; @JsonIgnore @NotNull @Size(min = 8, max = 255, message = "Password have to be grater than 8 characters") private String password; @NotNull private boolean enabled = true; @NotNull private boolean credentialsexpired = false; @NotNull private boolean expired = false; @NotNull private boolean locked = false; // @Formula("(select * from Role r where r.roleid in (select roleid from account_role ar where ar.accountid = accountid ))") @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "AccountRole", joinColumns = @JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "roleid", referencedColumnName = "roleid", insertable = false, updatable = false) ) private List<Role> roles; @CreatedDate @Column(name = "createddate") private Date createddate; @CreatedBy @Column(name = "createdby") private String createdby; @LastModifiedDate @Column(name = "lastmodifieddate") private Date lastmodifieddate; @LastModifiedBy @Column(name = "lastmodifiedby") private String lastmodifiedby; }
角色
Role.java
@Data @Entity @EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效 public class Role { @Id private String roleid; @NotNull private String code; @NotNull private String label; @CreatedDate @Column(name = "createddate") private Date createddate; @CreatedBy @Column(name = "createdby") private String createdby; @LastModifiedDate @Column(name = "lastmodifieddate") private Date lastmodifieddate; @LastModifiedBy @Column(name = "lastmodifiedby") private String lastmodifiedby; }
配置 ResourceServer Security
OAuth2ResourceServerConfig.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * Created by samchu on 2017/2/17. */ @Configuration @EnableResourceServer // prePostEnabled 很重要,可以讓你配置 @PreAuthorize("#oauth2.hasScope('account')") 在任何需要的方法上 @EnableGlobalMethodSecurity(prePostEnabled = true) public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter { // 這邊配置這服務的 resourceId ,當 jwt 中不含符合的 resourceId 則會拒絕操作 @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("account"); resources.tokenServices(tokenServices()); } // 配置哪些資源可以不用檢驗 Token ,這邊是對 swagger 的檔案無條件存取 及忘記密碼 passwordforget,其他的都要 @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.httpBasic().disable(); http.authorizeRequests().antMatchers( "/swagger-ui.html", "/v2/api-docs/**", "/swagger-resources/**", "/webjars/**", "/api/v1/passwordforget" ).permitAll(); // 或是也可以集中配置在這裡 //.antMatchers(HttpMethod.GET, "/my").access("#oauth2.hasScope('my-resource.read')") http.authorizeRequests().anyRequest().fullyAuthenticated(); } // 主要是配置 JWT 的密鑰 @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("ASDFASFsdfsdfsdfsfadsf234asdfasfdas"); return converter; } // 配置 JwtTokenStore @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } // 配置,使用預設的 DefaultTokenServices 就可以了,因為我們這邊只是做驗證而已 @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); return defaultTokenServices; } }
接下來就可以在 Service 這邊配置需要的權限
RoleService.java
import com.ps.dto.RoleDto; import com.ps.model.Role; import com.ps.repository.RoleRepository; import org.apache.commons.lang3.RandomStringUtils; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import java.util.List; /** * Created by samchu on 2017/2/17. */ @Service public class RoleService { @Autowired private RoleRepository roleRepository; // 這邊需要有 role 或是 role.readonly 的操作範圍的人才可以讀取角色列表 @PreAuthorize("#oauth2.hasScope('role') or #oauth2.hasScope('role.readonly')") public List<Role> listAll(){ return roleRepository.findAll(); } // 這邊是寫入角色,所以限定 role 的操作範圍才可以寫入 @PreAuthorize("#oauth2.hasScope('role')") public Role create(RoleDto roleDto) { ModelMapper modelMapper = new ModelMapper(); Role role = modelMapper.map(roleDto, Role.class); role.setRoleid(RandomStringUtils.randomAlphanumeric(10)); roleRepository.save(role); return role; } }
像是 hasScope('role') 或是 hasScope('role.readonly') 其實都是非常簡單好讀的敘述方式
那像用到 spring-boot-starter-data-rest 的功能,你其實可以像下面這樣設定
AccountRepository.java
import com.ps.model.Account; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.security.access.prepost.PreAuthorize; /** * Created by samchu on 2017/2/9. */ @RepositoryRestResource public interface AccountRepository extends JpaRepository<Account, String> { //@RestResource(path = "findByUsername") @Query("SELECT a FROM Account a WHERE a.username = :username") Account findByUsername(@Param("username") String username); // Prevents GET /accounts/:id @Override @RestResource(exported = true) @PreAuthorize("#oauth2.hasScope('account')") Account findOne(String id); // Prevents GET /account @Override @RestResource(exported = false) @PreAuthorize("#oauth2.hasScope('account')") Page<Account> findAll(Pageable pageable); // Prevents POST /account and PATCH /account/:id @Override @RestResource(exported = true) @PreAuthorize("#oauth2.hasScope('account')") Account save(Account s); // Prevents DELETE /account/:id @Override @RestResource(exported = false) @PreAuthorize("#oauth2.hasScope('account')") void delete(Account t); }
- 透過 RestResource(exported = false) 來配置此方法是否供外部透過 Rest 操作
- 特過 PreAuthorize 一樣可以設定符合操作的 scope 範圍
RestResource 預設是 exported = true,所以有的時候你自己會卡到外部內部權限問題
舉例說外部 rest 操作必須要有 account 操作範圍,但是某些時候是系統內部要操作,這時候你就沒有 token 來檢驗
不過你可以另外設定個系統操作的 Repository 像下面,這樣一來這個 Class 內部所有的方法都不對外露出,就可以放心用啦
AccountPrivateRepository.java
@Repository @RestResource(exported = false) public interface AccountPrivateRepository extends JpaRepository<Account, String> { @Query("SELECT a FROM Account a WHERE a.username = :username") Account findByUsername(@Param("username") String username); }
啟動程式
PsAccountApplication.java
@EnableJpaAuditing //@EnableTransactionManagement @SpringBootApplication public class PsAccountApplication { public static void main(String[] args) { SpringApplication.run(PsAccountApplication.class, args); } }
透過標準的 OAuth 跟 JWT 是不是省下很多自己開發的力氣呢?
使用 OAuth 不論是自用或開放兩相宜XD,使用 JWT 簡化驗證架構跟流程
雖然學習是有成本的,但是比起自己做一套授權系統,應該還是划算的很多
把力氣留在你的商業平台吧,這種基礎建設就盡量使用開源以及標準吧
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。