继上一次记录《SpringBoot 集成 Spring Security 自定义认证逻辑备忘》之后,这次记录一下SpringBoot 集成 Spring Security 自定义权限逻辑
主要分为三步:1、用户自定义认证通过后获取数据库栏目地址列表;2、添加自定义权限校验器 SecurityPermissionEvaluator; 3、注入自定义 SecurityPermissionEvaluator
具体代码如下:
1、在类 SecurityAuthenticationProvider 中添加栏目链接的设置
package com.lakey.framework.security.provider;
import com.lakey.common.constant.Constants;
import com.lakey.common.utils.LakeyTextUtils;
import com.lakey.common.utils.MD5Utils;
import com.lakey.framework.security.domain.SecurityUserInfo;
import com.lakey.framework.security.handler.SecurityAuthenticationSuccessHandler;
import com.lakey.project.sys.sysMenu.domain.SysMenu;
import com.lakey.project.sys.sysMenu.service.ISysMenuService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* SpringSecurity 自定义 AuthenticationProvider
*
* @author LiMing
* @date 2019-03-26
*/
@Component
public class SecurityAuthenticationProvider implements AuthenticationProvider {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 注入我们自己定义的用户信息获取对象
*/
@Qualifier("securityUserDetailsService")
@Autowired
private UserDetailsService userDetailService;
@Autowired
private ISysMenuService sysMenuService;
/**
* 认证用户信息
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();// 这个获取表单输入中的用户名
String password = (String) authentication.getCredentials();// 这个是表单中输入的密码
/** 判断用户是否存在 */
SecurityUserInfo userInfo = (SecurityUserInfo) userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;
if (userInfo == null) {
throw new UsernameNotFoundException("用户不存在");
}
logger.info("userInfo : " + userInfo.toString());
/** 判断密码是否正确 */
String encodePwd = MD5Utils.hash(userInfo.getSalt().concat(password));
if (!userInfo.getPassword().equals(encodePwd)) {
throw new BadCredentialsException("密码不正确");
}
/** 判断账号是否停用 */
if (!userInfo.isEnabled()) {
throw new DisabledException("账户不可用");
}
/** 判断账号是否过期 */
if (!userInfo.isAccountNonExpired()) {
throw new DisabledException("账户已过期");
}
/** 设置栏目链接 */
List<SysMenu> menus = SecurityAuthenticationProvider.getMenus(sysMenuService, userInfo.getId());
// Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
List<GrantedAuthority> authorities = new ArrayList <>();
for (SysMenu menu : menus) {
if (menu != null && LakeyTextUtils.isNotEmpty(menu.getUrl())&& LakeyTextUtils.isNotEmpty(menu.getPerms())) {
String authorityStr = menu.getUrl() + "__" + menu.getPerms();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authorityStr);
authorities.add(grantedAuthority);
}
}
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
/**
* 执行支持判断
*
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return true;// 返回 true ,表示支持执行
}
/**
* 返回管理员拥有的可见栏目列表
*
* @param sysMenuService
* @param userId
* @return
*/
public static List<SysMenu> getMenus(ISysMenuService sysMenuService, Integer userId) {
List<SysMenu> menus = new ArrayList<>();
// 查询所有可见目录列表
if (Constants.ADMINISTRATOR_ID.equals(userId)) {// 系统默认超级管理员账户拥有所有可见栏目权限
SysMenu paramMenu = new SysMenu();
paramMenu.setVisible("1");
menus = sysMenuService.selectSysMenuList(paramMenu);
}
// 查询管理员可见目录列表
else {
menus = sysMenuService.selectMenuListByUserId(userId);
}
return menus;
}
}
2、添加自定义权限校验器 SecurityPermissionEvaluator
package com.lakey.framework.security.provider;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
/**
* SpringSecurity 自定义 PermissionEvaluator
*
* @author LiMing
* @date 2019-03-27
*/
@Component
public class SecurityPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) authentication.getAuthorities();
for(GrantedAuthority authority : authorities) {
if(authority.getAuthority().equals(targetUrl + "__" + targetPermission)) {
return true;
}
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
return false;
}
}
3、注入自定义 SecurityPermissionEvaluator
package com.lakey.framework.security.config;
import com.lakey.framework.security.provider.SecurityPermissionEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* Spring Security 配置类
*
* @author LiMing
* @date 2019-03-26
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
@Value("${security.login.loginUrl}")
private String loginUrl;// 登录地址
@Value("${security.login.loginErrUrl}")
private String loginErrUrl;// 登录认证失败地址
/**
* 注入自定义的 AuthenticationProvider
*/
@Autowired
private AuthenticationProvider provider;
/**
* 注入自定义的 AuthenticationSuccessHandler
*/
@Autowired
private AuthenticationSuccessHandler securityAuthenticationSuccessHandler;
/**
* 注入自定义的 AuthenticationFailureHandler
*/
@Autowired
private AuthenticationFailureHandler securityAuthenticationFailHandler;
/**
* 访问权限配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
/* 禁用csrf模式 */
http.csrf().disable();
/* 允许同源 iframe */
http.headers().frameOptions().sameOrigin();
/* 登录页面链接、登录表单链接、登录失败页面链接配置 */
http.formLogin().loginPage(loginUrl).loginProcessingUrl("/login-process")
.successHandler(securityAuthenticationSuccessHandler)
.failureHandler(securityAuthenticationFailHandler)
.permitAll() // 登录页面链接、登录表单链接、登录失败页面链接配置
.and()
.authorizeRequests()
.antMatchers("/css/**").permitAll() // css 静态资源配置
.antMatchers("/fonts/**").permitAll() // fonts 静态资源配置
.antMatchers("/img/**").permitAll() // img 静态资源配置
.antMatchers("/js/**").permitAll() // js 静态资源配置
.antMatchers("/plugins/**").permitAll() // js 插件静态资源配置
.antMatchers(loginErrUrl).permitAll() // 登录错误链接配置
.anyRequest().authenticated();
/* 默认的退出的url地址是/logout,默认的退出成功跳转的url地址是/login?logout */
http.logout();
}
/**
* 动态认证
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
}
/**
* 注入自定义 PermissionEvaluator
*/
@Bean
public DefaultWebSecurityExpressionHandler myWebSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new SecurityPermissionEvaluator());
return handler;
}
}