生活随笔
收集整理的這篇文章主要介紹了
spring security默认登录页面登录用户,和自定义数据源
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、默認登錄頁面
請求 /hello 接口,在引入 spring security 之后會先經過一些列過濾器 在請求到達 FilterSecurityInterceptor時,發現請求并未認證。請求攔截下來,并拋出 AccessDeniedException 異常。 拋出 AccessDeniedException 的異常會被 ExceptionTranslationFilter 捕獲,這個 Filter 中會調用 LoginUrlAuthenticationEntryPoint#commence 方法給客戶端返回302,要求客戶端進行重定向到 /login 頁面。 客戶端發送 /login 請求。 /login 請求會再次被攔截器中 DefaultLoginPageGeneratingFilter 攔截到,并在攔截器中返回生成登錄頁面。
private String generateLoginPageHtml ( HttpServletRequest request
, boolean loginError
, boolean logoutSuccess
) { String errorMsg
= "Invalid credentials" ; if ( loginError
) { HttpSession session
= request
. getSession ( false ) ; if ( session
!= null ) { AuthenticationException ex
= ( AuthenticationException ) session
. getAttribute ( "SPRING_SECURITY_LAST_EXCEPTION" ) ; errorMsg
= ex
!= null ? ex
. getMessage ( ) : "Invalid credentials" ; } } String contextPath
= request
. getContextPath ( ) ; StringBuilder sb
= new StringBuilder ( ) ; sb
. append ( "<!DOCTYPE html>\n" ) ; sb
. append ( "<html lang=\"en\">\n" ) ; sb
. append ( " <head>\n" ) ; sb
. append ( " <meta charset=\"utf-8\">\n" ) ; sb
. append ( " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n" ) ; sb
. append ( " <meta name=\"description\" content=\"\">\n" ) ; sb
. append ( " <meta name=\"author\" content=\"\">\n" ) ; sb
. append ( " <title>Please sign in</title>\n" ) ; sb
. append ( " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n" ) ; sb
. append ( " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n" ) ; sb
. append ( " </head>\n" ) ; sb
. append ( " <body>\n" ) ; sb
. append ( " <div class=\"container\">\n" ) ; if ( this . formLoginEnabled
) { sb
. append ( " <form class=\"form-signin\" method=\"post\" action=\"" + contextPath
+ this . authenticationUrl
+ "\">\n" ) ; sb
. append ( " <h2 class=\"form-signin-heading\">Please sign in</h2>\n" ) ; sb
. append ( createError ( loginError
, errorMsg
) + createLogoutSuccess ( logoutSuccess
) + " <p>\n" ) ; sb
. append ( " <label for=\"username\" class=\"sr-only\">Username</label>\n" ) ; sb
. append ( " <input type=\"text\" id=\"username\" name=\"" + this . usernameParameter
+ "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n" ) ; sb
. append ( " </p>\n" ) ; sb
. append ( " <p>\n" ) ; sb
. append ( " <label for=\"password\" class=\"sr-only\">Password</label>\n" ) ; sb
. append ( " <input type=\"password\" id=\"password\" name=\"" + this . passwordParameter
+ "\" class=\"form-control\" placeholder=\"Password\" required>\n" ) ; sb
. append ( " </p>\n" ) ; sb
. append ( this . createRememberMe ( this . rememberMeParameter
) + this . renderHiddenInputs ( request
) ) ; sb
. append ( " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n" ) ; sb
. append ( " </form>\n" ) ; } if ( this . openIdEnabled
) { sb
. append ( " <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath
+ this . openIDauthenticationUrl
+ "\">\n" ) ; sb
. append ( " <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n" ) ; sb
. append ( createError ( loginError
, errorMsg
) + createLogoutSuccess ( logoutSuccess
) + " <p>\n" ) ; sb
. append ( " <label for=\"username\" class=\"sr-only\">Identity</label>\n" ) ; sb
. append ( " <input type=\"text\" id=\"username\" name=\"" + this . openIDusernameParameter
+ "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n" ) ; sb
. append ( " </p>\n" ) ; sb
. append ( this . createRememberMe ( this . openIDrememberMeParameter
) + this . renderHiddenInputs ( request
) ) ; sb
. append ( " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n" ) ; sb
. append ( " </form>\n" ) ; } Iterator var7
; Entry relyingPartyUrlToName
; String url
; String partyName
; if ( this . oauth2LoginEnabled
) { sb
. append ( "<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>" ) ; sb
. append ( createError ( loginError
, errorMsg
) ) ; sb
. append ( createLogoutSuccess ( logoutSuccess
) ) ; sb
. append ( "<table class=\"table table-striped\">\n" ) ; var7
= this . oauth2AuthenticationUrlToClientName
. entrySet ( ) . iterator ( ) ; while ( var7
. hasNext ( ) ) { relyingPartyUrlToName
= ( Entry ) var7
. next ( ) ; sb
. append ( " <tr><td>" ) ; url
= ( String ) relyingPartyUrlToName
. getKey ( ) ; sb
. append ( "<a href=\"" ) . append ( contextPath
) . append ( url
) . append ( "\">" ) ; partyName
= HtmlUtils . htmlEscape ( ( String ) relyingPartyUrlToName
. getValue ( ) ) ; sb
. append ( partyName
) ; sb
. append ( "</a>" ) ; sb
. append ( "</td></tr>\n" ) ; } sb
. append ( "</table>\n" ) ; } if ( this . saml2LoginEnabled
) { sb
. append ( "<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>" ) ; sb
. append ( createError ( loginError
, errorMsg
) ) ; sb
. append ( createLogoutSuccess ( logoutSuccess
) ) ; sb
. append ( "<table class=\"table table-striped\">\n" ) ; var7
= this . saml2AuthenticationUrlToProviderName
. entrySet ( ) . iterator ( ) ; while ( var7
. hasNext ( ) ) { relyingPartyUrlToName
= ( Entry ) var7
. next ( ) ; sb
. append ( " <tr><td>" ) ; url
= ( String ) relyingPartyUrlToName
. getKey ( ) ; sb
. append ( "<a href=\"" ) . append ( contextPath
) . append ( url
) . append ( "\">" ) ; partyName
= HtmlUtils . htmlEscape ( ( String ) relyingPartyUrlToName
. getValue ( ) ) ; sb
. append ( partyName
) ; sb
. append ( "</a>" ) ; sb
. append ( "</td></tr>\n" ) ; } sb
. append ( "</table>\n" ) ; } sb
. append ( "</div>\n" ) ; sb
. append ( "</body></html>" ) ; return sb
. toString ( ) ; } private String renderHiddenInputs ( HttpServletRequest request
) { StringBuilder sb
= new StringBuilder ( ) ; Iterator var3
= ( ( Map ) this . resolveHiddenInputs
. apply ( request
) ) . entrySet ( ) . iterator ( ) ; while ( var3
. hasNext ( ) ) { Entry < String , String > input
= ( Entry ) var3
. next ( ) ; sb
. append ( "<input name=\"" ) ; sb
. append ( ( String ) input
. getKey ( ) ) ; sb
. append ( "\" type=\"hidden\" value=\"" ) ; sb
. append ( ( String ) input
. getValue ( ) ) ; sb
. append ( "\" />\n" ) ; } return sb
. toString ( ) ; }
就是通過這種方式,Spring Security 默認過濾器中生成了登錄頁面,并返回!
二、默認登錄用戶
查看 SpringBootWebSecurityConfiguration.defaultSecurityFilterChain 方法表單登錄 處理登錄為 FormLoginConfigurer 類中 調用 這個類實例 查看類中 UsernamePasswordAuthenticationFilter#attempAuthentication 方法得知實際調用 AuthenticationManager 中 authenticate 方法 調用 ProviderManager 類中方法 authenticate
調用了 ProviderManager 實現類中 AbstractUserDetailsAuthenticationProvider類中方法
最終調用實現類 DaoAuthenticationProvider 類中方法比較
看到這里就知道默認實現是基于 InMemoryUserDetailsManager 這個類,也就是內存的實現!
三、自定義用戶數據源
InMemoryUserDetailsManager
看一下InMemoryUserDetailsManager的繼承圖
可以看到頂層是UserDetailService接口,接口中 loadUserByUserName 方法是用來在認證時進行用戶名認證方法,默認實現使用是內存實現,如果想要修改數據庫實現我們只需要自定義 UserDetailService 實現,最終返回 UserDetails 實例即可。
public interface UserDetailsService { UserDetails loadUserByUsername ( String username
) throws UsernameNotFoundException ;
}
在第一篇文章中,我們知道,springboot會基于SPI機制,導入一個UserDetailServiceAutoConfigutation,用于對UserDetailService進行配置。
@Configuration ( proxyBeanMethods
= false
)
@ConditionalOnClass ( { AuthenticationManager . class } )
@ConditionalOnBean ( { ObjectPostProcessor . class } )
@ConditionalOnMissingBean ( value
= { AuthenticationManager . class , AuthenticationProvider . class , UserDetailsService . class , AuthenticationManagerResolver . class } , type
= { "org.springframework.security.oauth2.jwt.JwtDecoder" , "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" , "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository" }
)
public class UserDetailsServiceAutoConfiguration { private static final String NOOP_PASSWORD_PREFIX
= "{noop}" ; private static final Pattern PASSWORD_ALGORITHM_PATTERN
= Pattern . compile ( "^\\{.+}.*$" ) ; private static final Log logger
= LogFactory . getLog ( UserDetailsServiceAutoConfiguration . class ) ; public UserDetailsServiceAutoConfiguration ( ) { } @Bean @Lazy public InMemoryUserDetailsManager inMemoryUserDetailsManager ( SecurityProperties properties
, ObjectProvider < PasswordEncoder > passwordEncoder
) { User user
= properties
. getUser ( ) ; List < String > roles
= user
. getRoles ( ) ; return new InMemoryUserDetailsManager ( new UserDetails [ ] { org. springframework. security. core. userdetails. User. withUsername ( user
. getName ( ) ) . password ( this . getOrDeducePassword ( user
, ( PasswordEncoder ) passwordEncoder
. getIfAvailable ( ) ) ) . roles ( StringUtils . toStringArray ( roles
) ) . build ( ) } ) ; } private String getOrDeducePassword ( User user
, PasswordEncoder encoder
) { String password
= user
. getPassword ( ) ; if ( user
. isPasswordGenerated ( ) ) { logger
. info ( String . format ( "%n%nUsing generated security password: %s%n" , user
. getPassword ( ) ) ) ; } return encoder
== null && ! PASSWORD_ALGORITHM_PATTERN
. matcher ( password
) . matches ( ) ? "{noop}" + password
: password
; }
}
從自動配置源碼中得知當 classpath 下存在 AuthenticationManager 類 當前項目中,系統沒有提供 AuthenticationManager.class、 AuthenticationProvider.class、UserDetailsService.class、 AuthenticationManagerResolver.class、實例
默認情況下都會滿足,此時Spring Security會提供一個 InMemoryUserDetailManager 實例
@ConfigurationProperties ( prefix
= "spring.security" )
public class SecurityProperties { private final User user
= new User ( ) ; public User getUser ( ) { return this . user
; } public static class User { private String name
= "user" ; private String password
= UUID
. randomUUID ( ) . toString ( ) ; private List < String > roles
= new ArrayList < > ( ) ; private boolean passwordGenerated
= true ; }
}
這就是默認生成 user 以及 uuid 密碼過程! 另外看明白源碼之后,就知道只要在配置文件中加入如下配置可以對內存中用戶和密碼進行覆蓋。
spring.security.user.name=root
spring.security.user.password=root
spring.security.user.roles=admin,users
自定義數據源
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService ( ) { InMemoryUserDetailsManager inMemoryUserDetailsManager
= new InMemoryUserDetailsManager ( ) ; UserDetails u1
= User . withUsername ( "zhangs" ) . password ( "{noop}111" ) . roles ( "USER" ) . build ( ) ; inMemoryUserDetailsManager
. createUser ( u1
) ; return inMemoryUserDetailsManager
; } @Override protected void configure ( AuthenticationManagerBuilder auth
) throws Exception { auth
. userDetailsService ( userDetailsService ( ) ) ; }
}
對象實現UserDetails 接口
public class User implements UserDetails { private Integer id
; private String username
; private String password
; private Boolean enabled
; private Boolean accountNonExpired
; private Boolean accountNonLocked
; private Boolean credentialsNonExpired
; private List < Role > roles
= new ArrayList < > ( ) ; @Override public Collection < ? extends GrantedAuthority > getAuthorities ( ) { List < GrantedAuthority > grantedAuthorities
= new ArrayList < > ( ) ; roles
. forEach ( role
-> grantedAuthorities
. add ( new SimpleGrantedAuthority ( role
. getName ( ) ) ) ) ; return grantedAuthorities
; } @Override public String getPassword ( ) { return password
; } @Override public String getUsername ( ) { return username
; } @Override public boolean isAccountNonExpired ( ) { return accountNonExpired
; } @Override public boolean isAccountNonLocked ( ) { return accountNonLocked
; } @Override public boolean isCredentialsNonExpired ( ) { return credentialsNonExpired
; } @Override public boolean isEnabled ( ) { return enabled
; }
}
Role 類
public class Role { private Integer id
; private String name
; private String nameZh
;
}
創建 UserDao 接口
@Mapper
public interface UserDao { User loadUserByUsername ( String username
) ; List < Role > getRolesByUid ( Integer uid
) ;
}
創建 UserDetailService 實例
@Component
public class MyUserDetailService implements UserDetailsService { private final UserDao userDao
; @Autowired public MyUserDetailService ( UserDao userDao
) { this . userDao
= userDao
; } @Override public UserDetails loadUserByUsername ( String username
) throws UsernameNotFoundException { User user
= userDao
. loadUserByUsername ( username
) ; if ( ObjectUtils . isEmpty ( user
) ) throw new RuntimeException ( "用戶不存在" ) ; user
. setRoles ( userDao
. getRolesByUid ( user
. getId ( ) ) ) ; return user
; }
}
配置 authenticationManager 使用自定義UserDetailService
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService
; @Autowired public WebSecurityConfigurer ( UserDetailsService userDetailsService
) { this . userDetailsService
= userDetailsService
; } @Override protected void configure ( AuthenticationManagerBuilder builder
) throws Exception { builder
. userDetailsService ( userDetailsService
) ; } @Override protected void configure ( HttpSecurity http
) throws Exception { }
}
數據庫表和mapper.xml按要求設計即可
總結
以上是生活随笔 為你收集整理的spring security默认登录页面登录用户,和自定义数据源 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。