Spring boot security 动态角色权限

原来项目一直使用spring mvc用的一套动态权限角色控制,现在项目更换spring boot来开发,原来的权限控制不起作用,于是使用spring boot security来实现动态角色和权限的配置功能。现功能只是为了实现cms后台的控制,还没有去做接口的动态权限控制,如果下一步需要并实现了功能会分享出来。
spring boot 使用的是2.1.0.RELEASE版本,废话不多说上配置文件。
pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.simmytech.cms
cms-platform
1.0.0-SNAPSHOT
war
cms-platform
http://maven.apache.org

org.springframework.boot
spring-boot-starter-parent
2.1.0.RELEASE


UTF-8



org.springframework.boot
spring-boot-starter-web


org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.0


mysql
mysql-connector-java


org.springframework.boot
spring-boot-starter-test
test


org.springframework.boot
spring-boot-starter-security


org.springframework.boot
spring-boot-starter-thymeleaf



org.thymeleaf.extras
thymeleaf-extras-springsecurity5


org.springframework.boot
spring-boot-devtools
true


org.springframework.boot
spring-boot-starter-tomcat
provided



cms


org.springframework.boot
spring-boot-maven-plugin


true
true




新建项目后看一下项目目录结构


目录结构

权限控制我们需要写的类有几个
1:com.simmytech.cms.config下的MyPasswordEncoder
2:com.simmytech.cms.config下的WebSecurityConfig
3:com.simmytech.cms.service下的CustomUserService
4:com.simmytech.cms.service下的MyAccessDecisionManager
5:com.simmytech.cms.service下的MyFilterSecurityInterceptor
6:com.simmytech.cms.service下的MyInvocationSecruityMetadataSourceService
下面我们先数据库用户、角色、权限表的设计和数据库的逻辑操作,然后再和secrity结合控制

数据库表一共由
数据库表

5个表组成,分别是用户表sys_user
用户表

角色表sys_role
角色表

权限表sys_permission
权限表

权限角色关联表sys_permission_role
权限角色关联表

角色用户关联表sys_role_user
角色用户关联表

现在看具体权限相关类的写法
MyPasswordEncoder类用户用户密码加密的类

package com.simmytech.cms.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class MyPasswordEncoder implements PasswordEncoder {
Log log = LogFactory.getLog(MyPasswordEncoder.class);
BCryptPasswordEncoder bean = new BCryptPasswordEncoder();
@Override
public String encode(CharSequence arg0) {
// TODO Auto-generated method stub
return bean.encode(arg0).toString();
}
@Override
public boolean matches(CharSequence arg0, String arg1) {
// TODO Auto-generated method stub
return bean.matches(arg0, arg1);
}
}

WebSecurityConfig类Security是否过滤的配置
package com.simmytech.cms.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import com.simmytech.cms.service.MyFilterSecurityInterceptor;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Autowired
UserDetailsService customUserService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService).passwordEncoder(new MyPasswordEncoder()); // user
// Details
// Service验证
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable()
.and().authorizeRequests().antMatchers("/dist/", "/plugins/","/favicon.ico").permitAll().anyRequest().authenticated() // 任何请求,登录后可以访问
.and().formLogin().loginPage("/login").defaultSuccessUrl("/").failureUrl("/login?error").permitAll() // 登录页面用户任意访问
.and().logout().permitAll(); // 注销行为任意访问
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
}

我的配置是可以iframe访问,静态资源的访问地址不拦截,登录地址,登录成功地址和登录失败的地址配置,其他访问地址全部需要经过权限拦截。
CustomUserService类登录根据用户名获取用户信息

package com.simmytech.cms.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.simmytech.cms.model.SysPermission;
import com.simmytech.cms.model.SysUser;
@Service
public class CustomUserService implements UserDetailsService { // 自定义UserDetailsService
@Autowired
private UserService userService;
public UserDetails loadUserByUsername(String username) {
SysUser user = userService.getUserByUserName(username);
if (user != null) {
List permissions = userService.getPermissionByUserId(user.getId());
List grantedAuthorities = new ArrayList<>();
for (SysPermission permission : permissions) {
if (permission != null && permission.getName() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
grantedAuthorities.add(grantedAuthority);
}
}
userService.updateUserLogintime(user.getId());
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}

MyAccessDecisionManager类

package com.simmytech.cms.service;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Iterator;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
throw new AccessDeniedException("当前访问没有权限");
}
ConfigAttribute c;
String needRole;
for(Iterator iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class clazz) {
return true;
}
}

MyFilterSecurityInterceptor类

package com.simmytech.cms.service;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}

MyInvocationSecurityMetadataSourceService类

package com.simmytech.cms.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import com.simmytech.cms.model.SysPermission;
import com.simmytech.cms.model.SysRole;
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Value("${simmytech.exurl}")
private String exurl;
@Autowired
private UserService userService;
@Override
public Collection getAttributes(Object object) throws IllegalArgumentException {
// 获取当前访问url
String url = ((FilterInvocation) object).getRequestUrl();
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
if(filter(url)){
return null;
}
List result = new ArrayList<>();
// 查询数据库url匹配的菜单
List menuList = userService.getPermissionByUrl(url);
if (menuList != null && menuList.size() > 0) {
for (SysPermission menu : menuList) {
// 查询拥有该菜单权限的角色列表
List roles = userService.getRoleByPermissionId(menu.getId());
if (roles != null && roles.size() > 0) {
for (SysRole role : roles) {
ConfigAttribute conf = new SecurityConfig(role.getName());
result.add(conf);
}
}
}
}
if(result.isEmpty()){
result.add(new SecurityConfig("ROLE_EMPTY"));
}
return result;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class clazz) {
return true;
}
private boolean filter(String url){
String[] urls=exurl.split(",");
for(String temp:urls){
if(url.indexOf(temp)==0){
return true;
}
}
return false;
}
}

下面开始看controller和登录页的配置

package com.simmytech.cms.web;
import java.util.Calendar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.simmytech.cms.model.SysUser;
import com.simmytech.cms.service.UserService;
import com.simmytech.cms.vo.MenuVo;
@Controller
public class HomeController {
@Autowired
private UserService userService;
@RequestMapping("/")
public String index(Model model){
return "index";
}
@RequestMapping("/login")
public String login(){
return "login";
}
}

controller包含登录页和首页

现在看登录页的登录form































下面看登录成功首页的退出登录功能



当前文章没有包含userService的写法,当然这个可以根据自己的业务来实现。
如果有需要页面权限标签的功能我会单独的文章来展现,这里就不说明了。

动态配置用户角色权限直接的关系需要大家自己来实现了。

你可能感兴趣的:(Spring boot security 动态角色权限)