Cara membuat metode kustom untuk digunakan dalam anotasi bahasa ekspresi keamanan musim semi

92

Saya ingin membuat kelas yang menambahkan metode kustom untuk digunakan dalam bahasa ekspresi keamanan musim semi untuk otorisasi berbasis metode melalui anotasi.

Misalnya, saya ingin membuat metode kustom seperti 'customMethodReturningBoolean' untuk digunakan seperti ini:

  @PreAuthorize("customMethodReturningBoolean()")
  public void myMethodToSecure() { 
    // whatever
  }

Pertanyaan saya adalah ini. Jika memungkinkan, kelas apa yang harus saya subkelas untuk membuat metode kustom saya, bagaimana saya akan mengonfigurasinya di file konfigurasi spring xml dan datanglah seseorang memberi saya contoh metode kustom yang digunakan dengan cara ini?

Paul D. Eden
sumber
1
Saya tidak punya waktu untuk mengetik jawaban sekarang tapi saya mengikuti panduan ini dan itu bekerja dengan sangat baik: baeldung.com/… Saya menggunakan Spring Security 5.1.1.
Paul

Jawaban:

35

Anda harus membuat subkelas dua kelas.

Pertama, setel penangan ekspresi metode baru

<global-method-security>
  <expression-handler ref="myMethodSecurityExpressionHandler"/>
</global-method-security>

myMethodSecurityExpressionHandlerakan menjadi subkelas DefaultMethodSecurityExpressionHandleryang menggantikannya createEvaluationContext(), menyetel subkelas dari MethodSecurityExpressionRootpada MethodSecurityEvaluationContext.

Sebagai contoh:

@Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
    MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, parameterNameDiscoverer);
    MethodSecurityExpressionRoot root = new MyMethodSecurityExpressionRoot(auth);
    root.setTrustResolver(trustResolver);
    root.setPermissionEvaluator(permissionEvaluator);
    root.setRoleHierarchy(roleHierarchy);
    ctx.setRootObject(root);

    return ctx;
}
sourcedelica.dll
sumber
Hmm, sepertinya ide yang bagus, tetapi semua properti DefaultMethodSecurityExpressionHandler bersifat pribadi tanpa aksesor, jadi saya ingin tahu bagaimana Anda memperluas kelas tanpa refleksi yang buruk. Terima kasih.
Joseph Lust
1
Maksud Anda trustResolver, dll? Mereka semua memiliki setter di DefaultMethodSecurityExpressionHandler (setidaknya di Spring Security 3.0) See: static.springsource.org/spring-security/site/apidocs/org/...
sourcedelica
3
@ericacm Bagaimana cara Anda MethodSecurityExpressionRootmenjadi paket pribadi ?
C. Ross
176

Tak satu pun dari teknik yang disebutkan akan berhasil lagi. Tampaknya Spring telah berusaha keras untuk mencegah pengguna menimpa SecurityExpressionRoot.

EDIT 11/19/14 Setup Spring untuk menggunakan anotasi keamanan:

<beans ... xmlns:sec="http://www.springframework.org/schema/security" ... >
...
<sec:global-method-security pre-post-annotations="enabled" />

Buat kacang seperti ini:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(String key) {
        return true;
    }
}

Kemudian lakukan sesuatu seperti ini di jsp Anda:

<sec:authorize access="@mySecurityService.hasPermission('special')">
    <input type="button" value="Special Button" />
</sec:authorize>

Atau beri anotasi pada metode:

@PreAuthorize("@mySecurityService.hasPermission('special')")
public void doSpecialStuff() { ... }

Selain itu, Anda dapat menggunakan Spring Expression Language di @PreAuthorizeanotasi Anda untuk mengakses autentikasi saat ini serta argumen metode.

Sebagai contoh:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, String foo) { ... }
}

Kemudian perbarui @PreAuthorizeagar sesuai dengan tanda tangan metode baru:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
sumber
6
@Bosh dalam metode hasPermission Anda, Anda dapat menggunakan Authentication auth = SecurityContextHolder.getContext().getAuthentication();untuk mendapatkan token otentikasi saat ini.
James Watkins
2
Terima kasih James atas jawaban Anda. Apakah saya harus mendefinisikan mySecurityService di file konfigurasi musim semi?
WowBow
2
Anda tidak perlu mendefinisikan mySecurityService dalam file XML apa pun jika Anda memiliki pengaturan pemindaian komponen untuk paket tempat layanan itu berada. Jika Anda tidak memiliki pemindaian komponen yang cocok, maka Anda harus menggunakan definisi kacang xml. @PreAuthorize berasal dari org.springframework.security
James Watkins
3
Anda mungkin perlu menentukan nama kacang untuk penjelasan seperti ini: @Component ("mySecurityService") atau menggunakan anotasi @Named.
James Watkins
1
@VJS Silakan lihat hasil edit yang saya buat. Anda perlu mengonfigurasi pegas untuk menggunakan anotasi ini. Saya terkejut tidak ada orang lain yang mengeluh tentang detail penting yang hilang ini :)
James Watkins
14

Terima kasih ericacm , tetapi tidak berfungsi karena beberapa alasan:

  • Properti DefaultMethodSecurityExpressionHandler bersifat pribadi (visibilitas pantulan kludges tidak diinginkan)
  • Setidaknya di Eclipse saya, saya tidak bisa menyelesaikan objek MethodSecurityEvaluationContext

Perbedaannya adalah kita memanggil metode createEvaluationContext yang sudah ada, lalu menambahkan objek root kustom. Akhirnya saya baru saja mengembalikan tipe objek StandardEvaluationContext karena MethodSecurityEvaluationContext tidak akan menyelesaikan di compiler (keduanya dari antarmuka yang sama). Ini adalah kode yang sekarang saya miliki dalam produksi.

Jadikan MethodSecurityExpressionHandler menggunakan root kustom kami:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler  {

    // parent constructor
    public CustomMethodSecurityExpressionHandler() {
        super();
    }

    /**
     * Custom override to use {@link CustomSecurityExpressionRoot}
     * 
     * Uses a {@link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
     * configures it with a {@link MethodSecurityExpressionRoot} instance as the expression root object.
     */
    @Override
    public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
        // due to private methods, call original method, then override it's root with ours
        StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi);
        ctx.setRootObject( new CustomSecurityExpressionRoot(auth) );
        return ctx;
    }
}

Ini menggantikan root default dengan memperluas SecurityExpressionRoot . Di sini saya telah mengganti nama hasRole menjadi hasEntitlement:

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot  {

    // parent constructor
    public CustomSecurityExpressionRoot(Authentication a) {
        super(a);
    }

    /**
     * Pass through to hasRole preserving Entitlement method naming convention
     * @param expression
     * @return boolean
     */
    public boolean hasEntitlement(String expression) {
        return hasRole(expression);
    }

}

Terakhir, perbarui securityContext.xml (dan pastikan itu direferensikan dari applcationContext.xml Anda):

<!-- setup method level security using annotations -->
<security:global-method-security
        jsr250-annotations="disabled"
        secured-annotations="disabled"
        pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<!--<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">-->
<bean id="expressionHandler" class="com.yourSite.security.CustomMethodSecurityExpressionHandler" />

Catatan: anotasi @Secured tidak akan menerima penggantian ini karena dijalankan melalui penangan validasi yang berbeda. Jadi, di xml di atas saya menonaktifkannya untuk mencegah kebingungan nanti.

Joseph Lust
sumber