spring-security权限控制详解
在本例中,主要講解spring-boot與spring-security的集成,實(shí)現(xiàn)方式為:
- 將用戶、權(quán)限、資源(url)采用數(shù)據(jù)庫(kù)存儲(chǔ)
- 自定義過(guò)濾器,代替原有的 FilterSecurityInterceptor
- 自定義實(shí)現(xiàn) UserDetailsService、AccessDecisionManager和InvocationSecurityMetadataSourceService,并在配置文件進(jìn)行相應(yīng)的配置
GitHub 地址:https://github.com/fp2952/spring-boot-security-demo
用戶角色表(基于RBAC權(quán)限控制)
- 用戶表(base_user)
| ID | varchar | 32 |
| USER_NAME | varchar | 50 |
| USER_PASSWORD | varchar | 100 |
| NIKE_NAME | varchar | 50 |
| STATUS | int | 11 |
- 用戶角色表(base_user_role)
| ID | varchar | 32 |
| USER_ID | varchar | 32 |
| ROLE_ID | varchar | 32 |
- 角色表(base_role)
| ID | varchar | 32 |
| ROLE_CODE | varchar | 32 |
| ROLE_NAME | varchar | 64 |
- 角色菜單表(base_role_menu)
| ID | varchar | 32 |
| ROLE_ID | varchar | 32 |
| MENU_ID | varchar | 32 |
- 菜單表(base_menu)
| ID | varchar | 32 |
| MENU_URL | varchar | 120 |
| MENU_SEQ | varchar | 120 |
| MENU_PARENT_ID | varchar | 32 |
| MENU_NAME | varchar | 50 |
| MENU_ICON | varchar | 20 |
| MENU_ORDER | int | 11 |
| IS_LEAF | varchar | 20 |
實(shí)現(xiàn)主要配置類
實(shí)現(xiàn)AbstractAuthenticationProcessingFilter
用于用戶表單驗(yàn)證,內(nèi)部調(diào)用了authenticationManager完成認(rèn)證,根據(jù)認(rèn)證結(jié)果執(zhí)行successfulAuthentication或者unsuccessfulAuthentication,無(wú)論成功失敗,一般的實(shí)現(xiàn)都是轉(zhuǎn)發(fā)或者重定向等處理。
@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//獲取表單中的用戶名和密碼String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();//組裝成username+password形式的tokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);//交給內(nèi)部的AuthenticationManager去認(rèn)證,并返回認(rèn)證信息return this.getAuthenticationManager().authenticate(authRequest);}AuthenticationManager
AuthenticationManager是一個(gè)用來(lái)處理認(rèn)證(Authentication)請(qǐng)求的接口。在其中只定義了一個(gè)方法authenticate(),該方法只接收一個(gè)代表認(rèn)證請(qǐng)求的Authentication對(duì)象作為參數(shù),如果認(rèn)證成功,則會(huì)返回一個(gè)封裝了當(dāng)前用戶權(quán)限等信息的Authentication對(duì)象進(jìn)行返回。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
在Spring Security中,AuthenticationManager的默認(rèn)實(shí)現(xiàn)是ProviderManager,而且它不直接自己處理認(rèn)證請(qǐng)求,而是委托給其所配置的AuthenticationProvider列表,然后會(huì)依次使用每一個(gè)AuthenticationProvider進(jìn)行認(rèn)證,如果有一個(gè)AuthenticationProvider認(rèn)證后的結(jié)果不為null,則表示該AuthenticationProvider已經(jīng)認(rèn)證成功,之后的AuthenticationProvider將不再繼續(xù)認(rèn)證。然后直接以該AuthenticationProvider的認(rèn)證結(jié)果作為ProviderManager的認(rèn)證結(jié)果。如果所有的AuthenticationProvider的認(rèn)證結(jié)果都為null,則表示認(rèn)證失敗,將拋出一個(gè)ProviderNotFoundException。
校驗(yàn)認(rèn)證請(qǐng)求最常用的方法是根據(jù)請(qǐng)求的用戶名加載對(duì)應(yīng)的UserDetails,然后比對(duì)UserDetails的密碼與認(rèn)證請(qǐng)求的密碼是否一致,一致則表示認(rèn)證通過(guò)。
Spring Security內(nèi)部的DaoAuthenticationProvider就是使用的這種方式。其內(nèi)部使用UserDetailsService來(lái)負(fù)責(zé)加載UserDetails。在認(rèn)證成功以后會(huì)使用加載的UserDetails來(lái)封裝要返回的Authentication對(duì)象,加載的UserDetails對(duì)象是包含用戶權(quán)限等信息的。認(rèn)證成功返回的Authentication對(duì)象將會(huì)保存在當(dāng)前的SecurityContext中。
實(shí)現(xiàn)UserDetailsService
UserDetailsService只定義了一個(gè)方法 loadUserByUsername,根據(jù)用戶名可以查到用戶并返回的方法。
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {logger.debug("權(quán)限框架-加載用戶");List<GrantedAuthority> auths = new ArrayList<>();BaseUser baseUser = new BaseUser();baseUser.setUserName(username);baseUser = baseUserService.selectOne(baseUser);if (baseUser == null) {logger.debug("找不到該用戶 用戶名:{}", username);throw new UsernameNotFoundException("找不到該用戶!");}if(baseUser.getStatus()==2){logger.debug("用戶被禁用,無(wú)法登陸 用戶名:{}", username);throw new UsernameNotFoundException("用戶被禁用!");}List<BaseRole> roles = baseRoleService.selectRolesByUserId(baseUser.getId());if (roles != null) {//設(shè)置角色名稱for (BaseRole role : roles) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleCode());auths.add(authority);}}return new org.springframework.security.core.userdetails.User(baseUser.getUserName(), baseUser.getUserPassword(), true, true, true, true, auths);}實(shí)現(xiàn)AbstractSecurityInterceptor
訪問(wèn)url時(shí),會(huì)被AbstractSecurityInterceptor攔截器攔截,然后調(diào)用FilterInvocationSecurityMetadataSource的方法來(lái)獲取被攔截url所需的全部權(quán)限,再調(diào)用授權(quán)管理器AccessDecisionManager鑒權(quán)。
public class CustomSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {private FilterInvocationSecurityMetadataSource securityMetadataSource;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi);}@Overridepublic void destroy() {}@Overridepublic Class<?> getSecureObjectClass() {return FilterInvocation.class;}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return this.securityMetadataSource;}public void invoke(FilterInvocation fi) throws IOException {InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());} catch (ServletException e) {super.afterInvocation(token, null);}}public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {return securityMetadataSource;}public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {this.securityMetadataSource = securityMetadataSource;} }FilterInvocationSecurityMetadataSource 獲取所需權(quán)限
@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//獲取當(dāng)前訪問(wèn)urlString url = ((FilterInvocation) object).getRequestUrl();int firstQuestionMarkIndex = url.indexOf("?");if (firstQuestionMarkIndex != -1) {url = url.substring(0, firstQuestionMarkIndex);}List<ConfigAttribute> result = new ArrayList<>();try {//設(shè)置不攔截if (propertySourceBean.getProperty("security.ignoring") != null) {String[] paths = propertySourceBean.getProperty("security.ignoring").toString().split(",");//判斷是否符合規(guī)則for (String path: paths) {String temp = StringUtil.clearSpace(path);if (matcher.match(temp, url)) {return SecurityConfig.createList("ROLE_ANONYMOUS");}}}//如果不是攔截列表里的, 默認(rèn)需要ROLE_ANONYMOUS權(quán)限if (!isIntercept(url)) {return SecurityConfig.createList("ROLE_ANONYMOUS");}//查詢數(shù)據(jù)庫(kù)url匹配的菜單List<BaseMenu> menuList = baseMenuService.selectMenusByUrl(url);if (menuList != null && menuList.size() > 0) {for (BaseMenu menu : menuList) {//查詢擁有該菜單權(quán)限的角色列表List<BaseRole> roles = baseRoleService.selectRolesByMenuId(menu.getId());if (roles != null && roles.size() > 0) {for (BaseRole role : roles) {ConfigAttribute conf = new SecurityConfig(role.getRoleCode());result.add(conf);}}}}} catch (Exception e) {e.printStackTrace();}return result;}/*** 判斷是否需要過(guò)濾* @param url* @return*/public boolean isIntercept(String url) {String[] filterPaths = propertySourceBean.getProperty("security.intercept").toString().split(",");for (String filter: filterPaths) {if (matcher.match(StringUtil.clearSpace(filter), url) & !matcher.match(indexUrl, url)) {return true;}}return false;}AccessDecisionManager 鑒權(quán)
@Overridepublic void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {if (collection == null) {return;}for (ConfigAttribute configAttribute : collection) {String needRole = configAttribute.getAttribute();for (GrantedAuthority ga : authentication.getAuthorities()) {if (needRole.trim().equals(ga.getAuthority().trim()) || needRole.trim().equals("ROLE_ANONYMOUS")) {return;}}}throw new AccessDeniedException("無(wú)權(quán)限!");}配置 WebSecurityConfigurerAdapter
/*** spring-security配置*/ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate PropertySource propertySourceBean;@Overrideprotected void configure(HttpSecurity http) throws Exception {logger.debug("權(quán)限框架配置");String[] paths = null;//設(shè)置不攔截if (propertySourceBean.getProperty("security.ignoring") != null) {paths = propertySourceBean.getProperty("security.ignoring").toString().split(",");paths = StringUtil.clearSpace(paths);}//設(shè)置過(guò)濾器http // 根據(jù)配置文件放行無(wú)需驗(yàn)證的url.authorizeRequests().antMatchers(paths).permitAll().and().httpBasic()// 配置驗(yàn)證異常處理.authenticationEntryPoint(getCustomLoginAuthEntryPoint())// 配置登陸過(guò)濾器.and().addFilterAt(getCustomLoginFilter(), UsernamePasswordAuthenticationFilter.class)// 配置 AbstractSecurityInterceptor.addFilterAt(getCustomSecurityInterceptor(), FilterSecurityInterceptor.class)// 登出成功處理.logout().logoutSuccessHandler(getCustomLogoutSuccessHandler())// 關(guān)閉csrf.and().csrf().disable()// 其他所有請(qǐng)求都需要驗(yàn)證.authorizeRequests().anyRequest().authenticated()// 配置登陸url, 登陸頁(yè)面并無(wú)需驗(yàn)證.and().formLogin().loginProcessingUrl("/login").loginPage("/login.ftl").permitAll()// 登出.and().logout().logoutUrl("/logout").permitAll();logger.debug("配置忽略驗(yàn)證url");}@Autowired@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(getDaoAuthenticationProvider());}/*** spring security 配置* @return*/@Beanpublic CustomLoginAuthEntryPoint getCustomLoginAuthEntryPoint() {return new CustomLoginAuthEntryPoint();}/*** 用戶驗(yàn)證* @return*/@Beanpublic DaoAuthenticationProvider getDaoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService);provider.setHideUserNotFoundExceptions(false);provider.setPasswordEncoder(new BCryptPasswordEncoder());return provider;}/*** 登陸* @return*/@Beanpublic CustomLoginFilter getCustomLoginFilter() {CustomLoginFilter filter = new CustomLoginFilter();try {filter.setAuthenticationManager(this.authenticationManagerBean());} catch (Exception e) {e.printStackTrace();}filter.setAuthenticationSuccessHandler(getCustomLoginAuthSuccessHandler());filter.setAuthenticationFailureHandler(new CustomLoginAuthFailureHandler());return filter;}@Beanpublic CustomLoginAuthSuccessHandler getCustomLoginAuthSuccessHandler() {CustomLoginAuthSuccessHandler handler = new CustomLoginAuthSuccessHandler();if (propertySourceBean.getProperty("security.successUrl")!=null){handler.setAuthSuccessUrl(propertySourceBean.getProperty("security.successUrl").toString());}return handler;}/*** 登出* @return*/@Beanpublic CustomLogoutSuccessHandler getCustomLogoutSuccessHandler() {CustomLogoutSuccessHandler handler = new CustomLogoutSuccessHandler();if (propertySourceBean.getProperty("security.logoutSuccessUrl")!=null){handler.setLoginUrl(propertySourceBean.getProperty("security.logoutSuccessUrl").toString());}return handler;}/*** 過(guò)濾器* @return*/@Beanpublic CustomSecurityInterceptor getCustomSecurityInterceptor() {CustomSecurityInterceptor interceptor = new CustomSecurityInterceptor();interceptor.setAccessDecisionManager(new CustomAccessDecisionManager());interceptor.setSecurityMetadataSource(getCustomMetadataSourceService());try {interceptor.setAuthenticationManager(this.authenticationManagerBean());} catch (Exception e) {e.printStackTrace();}return interceptor;}@Beanpublic CustomMetadataSourceService getCustomMetadataSourceService() {CustomMetadataSourceService sourceService = new CustomMetadataSourceService();if (propertySourceBean.getProperty("security.successUrl")!=null){sourceService.setIndexUrl(propertySourceBean.getProperty("security.successUrl").toString());}return sourceService;} }轉(zhuǎn)載于:https://www.cnblogs.com/fp2952/p/8933107.html
總結(jié)
以上是生活随笔為你收集整理的spring-security权限控制详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [洛谷P2124] 奶牛美容
- 下一篇: java中的类型转换