Saya menggunakan Spring Security untuk mengamankan aplikasi web Struts2. Karena keterbatasan proyek, saya menggunakan Spring Security 2.06.
Tim saya membuat API Manajemen Pengguna khusus yang mengautentikasi pengguna setelah mengambil parameter nama pengguna dan kata sandi, dan mengembalikan objek pengguna khusus yang berisi daftar peran dan atribut lain seperti email, nama, dll.
Dari pemahaman saya, kasus penggunaan Spring Security yang khas menggunakan UserDetailsService default untuk mengambil objek UserDetails; objek ini akan berisi (antara lain) bidang kata sandi yang akan digunakan oleh kerangka kerja untuk mengautentikasi pengguna.
Dalam kasus saya, saya ingin membiarkan API kustom kami melakukan otentikasi, kemudian mengembalikan objek UserDetails kustom yang berisi peran dan atribut lainnya (email, dll).
Setelah beberapa penelitian, saya menemukan bahwa saya dapat melakukan ini melalui implementasi kustom AuthenticationProvider. Saya juga memiliki implementasi kustom dari UserDetailsService dan UserDetails.
Masalah saya adalah saya tidak benar-benar mengerti apa yang seharusnya saya kembalikan di CustomAuthenticationProvider. Apakah saya menggunakan objek UserDetailsService kustom saya di sini? Apakah itu bahkan diperlukan? Maaf, saya benar-benar bingung.
CustomAuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private Logger logger = Logger.getLogger(CustomAuthenticationProvider.class);
private UserDetailsService userDetailsService; //what am i supposed to do with this?
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal());
String password = String.valueOf(auth.getCredentials());
logger.info("username:" + username);
logger.info("password:" + password);
/* what should happen here? */
return null; //what do i return?
}
@Override
public boolean supports(Class aClass) {
return true; //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
applicationContext-security.xml:
<beans:bean id="customUserDetailsService" scope="prototype" class="com.test.testconsole.security.CustomUserDetailsService"/>
<beans:bean id="customAuthenticationProvider" class="com.test.testconsole.security.CustomAuthenticationProvider">
<custom-authentication-provider />
<beans:property name="userDetailsService" ref="customUserDetailsService" />
</beans:bean>
Untuk meringkas, inilah yang saya butuhkan:
Pengguna masuk melalui formulir web
Otentikasi pengguna menggunakan API manajemen pengguna internal
Untuk pengguna yang berhasil diautentikasi, isi GrantedAuthories, dll.
Kembalikan entitas pengguna yang berisi peran/otoritas, dan atribut lain seperti email, nama, dll. Saya kemudian harus dapat mengakses objek ini seperti ini ...
//spring security mendapatkan nama pengguna
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
userName = auth.getName(); //dapatkan nama pengguna yang login
logger.info("username: " + userName);
//keamanan mendapatkan peran pengguna
GrantedAuthority[] authorities = auth.getAuthorities();
userRole = authorities[0].getAuthority();
logger.info("peran pengguna: " + userRole);
Saya harap ini masuk akal. Setiap bantuan atau petunjuk akan dihargai!
Terima kasih!
Pembaruan:
Saya pikir, saya telah membuat beberapa kemajuan.
Saya memiliki objek Otentikasi kustom yang mengimplementasikan antarmuka Otentikasi:
public class CustomAuthentication implements Authentication {
String name;
GrantedAuthority[] authorities;
Object credentials;
Object details;
Object principal;
boolean authenticated;
public CustomAuthentication(String name, GrantedAuthority[] authorities, Object credentials, Object details, Object principal, boolean
authenticated){
this.name=name;
this.authorities=authorities;
this.details=details;
this.principal=principal;
this.authenticated=authenticated;
}
@Override
public GrantedAuthority[] getAuthorities() {
return new GrantedAuthority[0]; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public Object getCredentials() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public Object getDetails() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public Object getPrincipal() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public boolean isAuthenticated() {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public String getName() {
return null;
}
}
dan memperbarui kelas CustomerAuthenticationProvider saya:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal());
String password = String.valueOf(auth.getCredentials());
logger.info("username:" + username);
logger.info("password:" + password);
//no actual validation done at this time
GrantedAuthority[] authorities = new GrantedAuthorityImpl[1];
authorities[0] = new GrantedAuthorityImpl("ROLE_USER");
CustomAuthentication customAuthentication = new CustomAuthentication("TestMerchant",authorities,"details",username,password,true);
return customAuthentication;
//return new UsernamePasswordAuthenticationToken(username,password,authorities);
}
Ini berfungsi jika saya mengembalikan objek UsernamePasswordAuthenticationToken, tetapi jika saya mencoba mengembalikan CustomAuthentication, saya mendapatkan kesalahan berikut:
java.lang.ClassCastException: com.test.testconsole.security.CustomAuthentication cannot be cast to org.springframework.security.providers.UsernamePasswordAuthenticationToken
at com.test.testconsole.security.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:27)
at org.springframework.security.providers.ProviderManager.doAuthentication(ProviderManager.java:188)
at org.springframework.security.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:46)
at org.springframework.security.intercept.AbstractSecurityInterceptor.authenticateIfRequired(AbstractSecurityInterceptor.java:319)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:258)
at org.springframework.security.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106)
at org.springframework.security.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.SessionFixationProtectionFilter.doFilterHttp(SessionFixationProtectionFilter.java:67)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.ExceptionTranslationFilter.doFilterHttp(ExceptionTranslationFilter.java:101)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.providers.anonymous.AnonymousProcessingFilter.doFilterHttp(AnonymousProcessingFilter.java:105)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.rememberme.RememberMeProcessingFilter.doFilterHttp(RememberMeProcessingFilter.java:116)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter.doFilterHttp(SecurityContextHolderAwareRequestFilter.java:91)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.basicauth.BasicProcessingFilter.doFilterHttp(BasicProcessingFilter.java:174)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.AbstractProcessingFilter.doFilterHttp(AbstractProcessingFilter.java:278)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.logout.LogoutFilter.doFilterHttp(LogoutFilter.java:89)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.context.HttpSessionContextIntegrationFilter.doFilterHttp(HttpSessionContextIntegrationFilter.java:235)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.util.FilterChainProxy.doFilter(FilterChainProxy.java:175)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536)
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:915)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:539)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Ini seperti ada sesuatu yang mengharapkan bukan sembarang objek Autentikasi, tetapi implementasi spesifiknya - UsernamePasswordAuthenticationToken. Hal ini membuat saya berpikir bahwa saya mungkin kehilangan komponen khusus lainnya ... mungkin filter?
Jika Anda mengimplementasikan AuthenticationProvider
Anda sendiri, Anda tidak perlu mengimplementasikan UserDetailsService
jika Anda tidak menginginkannya. UserDetailsService
hanya menyediakan DAO standar untuk memuat informasi pengguna dan beberapa kelas lain dalam kerangka kerja diimplementasikan untuk menggunakannya.
Biasanya, untuk mengotentikasi menggunakan nama pengguna dan kata sandi, Anda akan menginstansiasi DaoAuthenticationProvider
dan menginjeksikannya dengan UserDetailsService
. Itu mungkin masih menjadi pendekatan terbaik Anda. Jika Anda mengimplementasikan provider Anda sendiri, Anda bertanggung jawab untuk memastikan pengguna telah memberikan kata sandi yang benar dan sebagainya. Namun, dalam beberapa kasus ini adalah pendekatan yang lebih sederhana.
Untuk menjawab komentar Anda "apa yang seharusnya terjadi di sini? " dalam kode Anda, akan menjadi sesuatu seperti
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal());
String password = String.valueOf(auth.getCredentials());
logger.info("username:" + username);
logger.info("password:" + password); // Don't log passwords in real app
// 1. Use the username to load the data for the user, including authorities and password.
YourUser user = ....
// 2. Check the passwords match (should use a hashed password here).
if (!user.getPassword().equals(password)) {
throw new BadCredentialsException("Bad Credentials");
}
// 3. Preferably clear the password in the user object before storing in authentication object
user.clearPassword();
// 4. Return an authenticated token, containing user data and authorities
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) ;
}
Objek pengguna kemudian akan dapat diakses dengan menggunakan
Authentication.getPrincipal()
dan Anda dapat mengakses properti tambahan (email, dll) dengan men-castingnya ke implementasi pengguna kustom Anda.
Bagaimana Anda memuat data pengguna terserah Anda. Semua yang dipedulikan Spring Security di sini adalah antarmuka AuthenticationProvider
.
Anda juga harus menyimpan kata sandi hash dan memvalidasi kata sandi yang disediakan menggunakan algoritma yang sama, bukan pemeriksaan kesetaraan sederhana.
terima kasih telah memposting ini Luke!
Menyelamatkan saya dari lebih banyak kerusakan otak.
Satu-satunya hal yang saya temui, bagi siapa saja yang peduli:
Pengaturan saya:
Ketika menggunakan pendekatan sederhana/elegant yang sangat dihargai yang disarankan Luke, TIDAK mengimplementasikan objek UserDetails (atau UserDetailsService) khusus - dan - menggunakan objek domain Pengguna Anda sendiri yang tidak memperluas sesuatu yang khusus, Anda harus mengambil langkah ekstra jika Anda menggunakan tag khusus "sec" dari spring security (di halaman Anda tentunya):
Ketika Anda membuat instantiate UsernamePasswordAuthenticationToken dasar, non-kustom, Anda HARUS mengoperkan instantiation dari sesuatu yang memperluas Principal, sekali lagi, jika Anda ingin tag celah kustom keamanan musim semi Anda berfungsi. Saya melakukan sesuatu seperti ini, untuk membuatnya sesederhana mungkin (mereferensikan nilai objek domain pengguna saya di mana berguna/sesuai):
def principalUser = new org.springframework.security.core.userdetails.User(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired,!user.accountLocked, authorities)
def token = new UsernamePasswordAuthenticationToken(principalUser, presentedPassword, authorities)
Ini harus memenuhi kondisi yang diuji di grails.plugins.springsecurity.SecurityTagLib.determineSource() jadi, Anda tahu, halaman Anda yang menggunakan <sec:loggedInUserInfo>
akan benar-benar dirender:
if (principal.metaClass.respondsTo(principal, 'getDomainClass')) {
return principal.domainClass
}
Jika tidak, jika Anda menginstansiasi UsernamePasswordAuthenticationToken dengan objek domain Pengguna Anda (seperti yang ditunjukkan Luke dalam contohnya), metode lib tag keamanan (determineSource()) hanya akan melakukan yang terbaik dan mengembalikan nilai (meta) dari org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass dan Anda akan mendapatkan kesalahan ketika tag mencari variabel anggota nama pengguna yang menyatakan:
Error executing tag <sec:ifLoggedIn>: Error executing tag <sec:loggedInUserInfo>: No such property: username for class: org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass
Singkatnya, tidak ada cara untuk menggunakan taglibs plugin spring-security-core di proyek grails saya, tidak ada cara untuk menggunakan taglibs DAN menggunakan kelas Pengguna domain kustom Anda untuk membuat token yang diteruskan dari filter Anda ke penyedia Anda.
Sekali lagi, satu baris kode tambahan adalah harga yang sangat kecil untuk dibayar :)