美文网首页
Spring boot security 动态角色权限

Spring boot security 动态角色权限

作者: 会议室窗边 | 来源:发表于2019-10-18 11:48 被阅读0次

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

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.simmytech.cms</groupId>
    <artifactId>cms-platform</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>cms-platform</name>
    <url>http://maven.apache.org</url>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.0.RELEASE</version>
    <relativePath />
    </parent>
    <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
    </dependency>
    </dependencies>
    <build>
    <finalName>cms</finalName>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>

    <fork>true</fork>
    <addResources>true</addResources>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>

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


    目录结构

    权限控制我们需要写的类有几个
    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<SysPermission> permissions = userService.getPermissionByUserId(user.getId());
    List<GrantedAuthority> 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<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    if(null== configAttributes || configAttributes.size() <=0) {
    throw new AccessDeniedException("当前访问没有权限");
    }
    ConfigAttribute c;
    String needRole;
    for(Iterator<ConfigAttribute> 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<ConfigAttribute> 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<ConfigAttribute> result = new ArrayList<>();
    // 查询数据库url匹配的菜单
    List<SysPermission> menuList = userService.getPermissionByUrl(url);
    if (menuList != null && menuList.size() > 0) {
    for (SysPermission menu : menuList) {
    // 查询拥有该菜单权限的角色列表
    List<SysRole> 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<ConfigAttribute> 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

    <form th:action="@{/login}" action="/login" method="post">
    <div class="input-group mb-3">
    <input type="text" name="username" class="form-control" placeholder="用户名">
    <div class="input-group-append">
    <div class="input-group-text">
    <span class="fas fa-envelope"></span>
    </div>
    </div>
    </div>
    <div class="input-group mb-3">
    <input type="password" name="password" class="form-control" placeholder="密码">
    <div class="input-group-append">
    <div class="input-group-text">
    <span class="fas fa-lock"></span>
    </div>
    </div>
    </div>
    <div class="row">
    <div class="col-8">
    <div class="icheck-primary">
    <input type="checkbox" id="remember">
    <label for="remember">
    Remember Me
    </label>
    </div>
    </div>

    <div class="col-4">
    <button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
    </div>

    </div>
    </form>

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

    <form th:action="@{/logout}" method="post">
    <input type="submit" class="btn btn-primary btn-sm" value="注销" />
    </form>

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

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

    相关文章

      网友评论

          本文标题:Spring boot security 动态角色权限

          本文链接:https://www.haomeiwen.com/subject/dohimctx.html