天天看点

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

点击上方“ Java资料站 ”,选择“标星公众号”

优质文章,第一时间送达

   刘宏缔的架构森林   |  作者

urlify.cn/UnYBbm  |  来源

一,动态权限管理的优点和缺点

1,优点:

  因为控制权限的数据保存在了mysql或其他存储系统中,

  可以动态修改权限控制,无需改动代码和重启应用,

  权限变更时灵活方便

2,缺点:

   权限的设置需要保存在外部存储系统,

   每次request时都需要查库处理,

   高并发时影响效率

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: [email protected]

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/securitydynamic            

2,项目功能说明

         通过修改mysql数据库中的数据,

         实现对权限验证的动态控制,无需修改代码和重启应用

3,项目结构:如图:

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...
spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

三,配置文件说明

1,pom.xml

<dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-securityartifactId>        dependency>                <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-thymeleafartifactId>        dependency>                <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-validationartifactId>        dependency>                <dependency>            <groupId>org.mybatis.spring.bootgroupId>            <artifactId>mybatis-spring-boot-starterartifactId>            <version>2.1.3version>        dependency>        <dependency>            <groupId>mysqlgroupId>            <artifactId>mysql-connector-javaartifactId>            <scope>runtimescope>        dependency>                <dependency>            <groupId>com.alibabagroupId>            <artifactId>fastjsonartifactId>            <version>1.2.72version>        dependency>           

2,application.properties:

#thymeleafspring.thymeleaf.cache=falsespring.thymeleaf.encoding=UTF-8spring.thymeleaf.mode=HTMLspring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.html#mysqlspring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=lhddemospring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#mybatismybatis.mapper-locations=classpath:/mapper/*Mapper.xmlmybatis.type-aliases-package=com.example.demo.mapper#errorserver.error.include-stacktrace=always#loglogging.level.org.springframework.web=tracelogging.level.org.springframework.security=debug           

3,数据库:

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

 建立各个表的sql:

CREATE TABLE `sys_user` ( `userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名', `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码', `nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称', PRIMARY KEY (`userId`), UNIQUE KEY `userName` (`userName`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'           
INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES(1, 'lhd', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '老刘'),(2, 'admin', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '管理员'),(3, 'merchant', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '商户老张');           
CREATE TABLE `sys_user_role` ( `urId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `userId` int(11) NOT NULL DEFAULT '0' COMMENT '用户id', `roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色id', PRIMARY KEY (`urId`), UNIQUE KEY `userId` (`userId`,`roleName`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'           
INSERT INTO `sys_user_role` (`urId`, `userId`, `roleName`) VALUES(1, 2, 'ADMIN'),(2, 3, 'MERCHANT');           
CREATE TABLE `sys_menu` ( `menuId` int(11) NOT NULL AUTO_INCREMENT, `pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, PRIMARY KEY (`menuId`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='method'           
INSERT INTO `sys_menu` (`menuId`, `pattern`) VALUES(1, '/home/**'),(2, '/login/**'),(3, '/js/**'),(4, '/admin/**'),(5, '/merchant/**');           
CREATE TABLE `sys_menu_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `menuId` int(11) DEFAULT NULL, `roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'role名字', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='menu角色对应表'           
INSERT INTO `sys_menu_role` (`id`, `menuId`, `roleName`) VALUES(1, 1, 'ALL'),(2, 2, 'ALL'),(3, 3, 'ALL'),(4, 4, 'ADMIN'),(5, 5, 'MERCHANT'),(6, 5, 'ADMIN');           

 说明:sys_user表中,3个用户的密码都是111111,仅供演示使用,大家在生产环境中一定不要这样设置

四,java代码说明

1,WebSecurityConfig.java

@[email protected] class WebSecurityConfig extends WebSecurityConfigurerAdapter {    private final static BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();    @Resource    private UserLoginFailureHandler userLoginFailureHandler;//登录失败的处理类    @Resource    private UserLoginSuccessHandler userLoginSuccessHandler;//登录成功的处理类    @Resource    private UserLogoutSuccessHandler userLogoutSuccessHandler;//退出成功的处理类    @Resource    private UserAccessDeniedHandler userAccessDeniedHandler;//无权访问的处理类    @Resource    private SecUserDetailService secUserDetailService;     //用户信息类,用来得到UserDetails    @Resource    private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;    @Resource    private CustomAccessDecisionManager customAccessDecisionManager;    //指定加密的方式,避免出现:There is no PasswordEncoder mapped for the id "null"    @Bean    public PasswordEncoder passwordEncoder(){//密码加密类        return  new BCryptPasswordEncoder();    }    @Override    protected void configure(HttpSecurity http) throws Exception {        //通过数据库的配置,动态判断当前用户是否可以访问当前url        http.authorizeRequests()                .withObjectPostProcessor(new ObjectPostProcessor() {                    @Override                    public O postProcess(O object) {                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);                        object.setAccessDecisionManager(customAccessDecisionManager);                        return object;                    }                });        //login        http.formLogin()                .loginPage("/login/login")                .loginProcessingUrl("/login/logined")//发送Ajax请求的路径                .usernameParameter("username")//请求验证参数                .passwordParameter("password")//请求验证参数                .failureHandler(userLoginFailureHandler)//验证失败处理                .successHandler(userLoginSuccessHandler)//验证成功处理                .permitAll(); //登录页面用户任意访问        //logout        http.logout()                .logoutUrl("/login/logout")                .logoutSuccessUrl("/login/logout")                .logoutSuccessHandler(userLogoutSuccessHandler)//登出处理                .deleteCookies("JSESSIONID")                .clearAuthentication(true)                .invalidateHttpSession(true)                .permitAll();        //其他任何请求,登录后可以访问        http.authorizeRequests().anyRequest().authenticated();        //accessdenied        http.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler);//无权限时的处理        //user detail        http.userDetailsService(secUserDetailService);  }    @Resource    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(secUserDetailService).passwordEncoder(new PasswordEncoder() {            @Override            public String encode(CharSequence charSequence) {                return ENCODER.encode(charSequence);            }            //密码匹配,看输入的密码经过加密与数据库中存放的是否一样            @Override            public boolean matches(CharSequence charSequence, String s) {                return ENCODER.matches(charSequence,s);            }        });    }}           

2,CustomAccessDecisionManager.java

@Componentpublic class CustomAccessDecisionManager implements AccessDecisionManager {    //比较用户的权限和url所需要的权限,确定是否可以访问    @Override    public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {        for (ConfigAttribute configAttribute : configAttributes) {            //如果是all,表示所有的都允许访问            if ("ALL".equals(configAttribute.getAttribute())) {                return;            }            //是否没有权限要求            if ("ROLE_def".equals(configAttribute.getAttribute())) {                if (authentication instanceof AnonymousAuthenticationToken) {                    System.out.println("匿名用户");                    throw new AccessDeniedException("权限不足,无法访问!");                } else {                    System.out.println("其他类型用户,可以访问");                    return;                }            }            Collection extends GrantedAuthority> authorities = authentication.getAuthorities();            for (GrantedAuthority authority : authorities) {                String userRole = authority.getAuthority();                //数据库中没有保存ROLE_,这里添加上                String menuRole = "ROLE_"+configAttribute.getAttribute();                if (userRole.equals(menuRole)) {                    //System.out.println("进入应用系统");                    return;                }            }        }        throw new AccessDeniedException("权限不足,无法访问!");    }    @Override    public boolean supports(ConfigAttribute attribute) {        return true;    }    @Override    public boolean supports(Class> clazz) {        return true;    }}           

用来判断当前用户是否有权限访问当前的url

3,CustomFilterInvocationSecurityMetadataSource.java

@Componentpublic class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {    AntPathMatcher pathMatcher = new AntPathMatcher();    @Autowired    private SysMenuService sysMenuService;    //得到所有的menu,    //查询出匹配当前url的所有role    @Override    public CollectiongetAttributes(Object object) throws IllegalArgumentException {        String requestUrl = ((FilterInvocation) object).getRequestUrl();        List menus = sysMenuService.getMenus();        for (SysMenu menu : menus) {            if (pathMatcher.match(menu.getPattern(), requestUrl)) {                List roles = menu.getRoles();                String[] roleStr = new String[roles.size()];                for (int i = 0; i < roles.size(); i++) {                    roleStr[i] = roles.get(i);                }                return SecurityConfig.createList(roleStr);            }        }        return SecurityConfig.createList("ROLE_def");    }    @Override    public CollectiongetAllConfigAttributes() {        return null;    }    @Override    public boolean supports(Class> clazz) {        return true;    }}           

通过查询数据库得到匹配当前url的role

4,SecUser.java

public class SecUser extends User {    //用户id    private int userid;    //用户昵称    private String nickname;    public SecUser(String username, String password, Collection extends GrantedAuthority> authorities) {        super(username, password, authorities);    }    public SecUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection extends GrantedAuthority> authorities) {        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);    }    public String getNickname() {        return nickname;    }    public void setNickname(String nickname) {        this.nickname = nickname;    }    public int getUserid() {        return userid;    }    public void setUserid(int userid) {        this.userid = userid;    }}           

spring security中User类的子类,增加了用户id和昵称,

需要保存到session中的信息,在这里扩展

目的是避免在每个页面上显示用户信息需要查数据库

5,SecUserDetailService.java

/** * Created by liuhongdi on 2020/07/09.*/@Component("SecUserDetailService")public class SecUserDetailService implements UserDetailsService{    @Resource    private SysUserService sysUserService;    @Override    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {        //查库        SysUser oneUser = sysUserService.getOneUserByUsername(s);//数据库查询 看用户是否存在        String encodedPassword = oneUser.getPassword();        Collection collection = new ArrayList<>();//权限集合        //用户权限:需要加 ROLE_        List<String> roles = oneUser.getRoles();        //System.out.println(roles);        for (String roleone : roles) {            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleone);            collection.add(grantedAuthority);       }        //增加用户的userid,nickname        SecUser user = new SecUser(s,encodedPassword,collection);        user.setUserid(oneUser.getUserId());        user.setNickname(oneUser.getNickName());        return user;    }}           

6,UserAccessDeniedHandler.java

@Component("UserAccessDeniedHandler")public class UserAccessDeniedHandler implements AccessDeniedHandler {    @Override    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,                       AccessDeniedException e) throws IOException, ServletException {        boolean isAjax = ServletUtil.isAjax();        //System.out.println("isajax:"+isAjax);        if (isAjax == true) {ServletUtil.printRestResult(RestResult.error(ResponseCode.ACCESS_DENIED));        } else {ServletUtil.printString(ResponseCode.ACCESS_DENIED.getMsg());        }    }}           

7,UserLoginFailureHandler.java

@Component("UserLoginFailureHandler")public class UserLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {    @Override    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,                                        AuthenticationException exception) throws IOException, ServletException {        //System.out.println("UserLoginFailureHandler");        ServletUtil.printRestResult(RestResult.error(ResponseCode.LOGIN_FAIL));    }}           

8,UserLoginSuccessHandler.java

@Component("UserLoginSuccessHandler")public class UserLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {        //System.out.println("UserLoginSuccessHandler");        ServletUtil.printRestResult(RestResult.success(0,"登录成功"));    }}           

9,UserLogoutSuccessHandler.java

@Component("UserLogoutSuccessHandler")public class UserLogoutSuccessHandler implements LogoutSuccessHandler{    @Override    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {        httpServletRequest.getSession().invalidate();        ServletUtil.printRestResult(RestResult.success(0,"退出成功"));    }}           

10,WebInterceptor.java

@Componentpublic class WebInterceptor extends HandlerInterceptorAdapter {    //如果view不为空,把登录信息传递给模板    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {        if (modelAndView != null) {            ModelMap modelMap = modelAndView.getModelMap();            SecUser currentUser = SessionUtil.getCurrentUser();            if (currentUser != null) {                modelMap.addAttribute("is_login","1");                modelMap.addAttribute("login_username",currentUser.getNickname());            } else {                modelMap.addAttribute("is_login","0");                modelMap.addAttribute("login_username","");            }        }    }}           

负责把传递页面公共部分显示的数据到模板

11,login.html

<html><head>    <meta content="text/html;charset=UTF-8"/>    <title>登录页面title>    <script type="text/javascript" language="JavaScript" src="/js/jquery-1.6.2.min.js">script>    <style type="text/css">        body {            padding-top: 50px;        }        .starter-template {            padding: 40px 15px;            text-align: center;        }style>        <meta name="_csrf" th:content="${_csrf.token}"/>        <meta name="_csrf_header" th:content="${_csrf.headerName}"/>head><body><nav class="navbar navbar-inverse navbar-fixed-top">    <div class="container">        <div id="navbar" class="collapse navbar-collapse">            <ul class="nav navbar-nav">                <li><a href="/home/home" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" > 首页 a>li>            ul>        div>    div>nav><div class="container">    <div class="starter-template">        <h2>使用账号密码登录h2>            <div class="form-group">                <label for="username">账号label>                <input type="text" class="form-control" id="username" name="username" value="" placeholder="账号" />            div>            <div class="form-group">                <label for="password">密码label>                <input type="password" class="form-control" id="password" name="password" placeholder="密码" />            div>            <button name="formsubmit" value="登录" onclick="go_login()" >登录button>    div>div><script>    function go_login(){        if ($("#username").val() == "") {            alert('用户名不可为空');            $("#username").focus();            return false;        }        if ($("#password").val() == "") {            alert('密码不可为空');            $("#password").focus();            return false;        }        var postdata = {            username:$("#username").val(),            password:$("#password").val(),        }        var csrfToken = $("meta[name='_csrf']").attr("content");        var csrfHeader = $("meta[name='_csrf_header']").attr("content");        $.ajax({            type:"POST",            //type:"GET",            url:"/login/logined",            data:postdata,            //返回数据的格式            datatype: "json",//"xml", "html", "script", "json", "jsonp", "text".            beforeSend: function(request) {                request.setRequestHeader(csrfHeader, csrfToken); // 添加  CSRF Token            },            success:function(data){                if (data.code == 0) {                    alert('login success:'+data.msg);                    window.location.href="/home/home" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" ;                } else {                    alert("failed:"+data.msg);                }            },            //调用执行后调用的函数            complete: function(XMLHttpRequest, textStatus){            },            //调用出错执行的函数            error: function(){                //请求出错处理                alert('error');            }        });    }script>body>html>           

12,页面上用到的其他代码,可以移步github.com上查看

五,测试效果

1,访问首页:

http://127.0.0.1:8080/home/home           

未登录时:

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

2,以普通用户lhd登录:

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

访问:管理员首页/商户首页,都会得到提示

无权访问           

访问修改密码 页面,可以访问

3,以merchant用户登录:

   role是MERCHANT

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

访问:管理员首页,提示:

无权访问           

访问商户首页:可以访问

访问修改密码 页面,可以访问 

4,以admin用户登录:

role是ADMIN

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

访问管理员首页:可以访问

访问商户首页:可以访问

访问修改密码 页面,可以访问

5,从mysql数据库sys_menu_role表中删除:meuid=5  rolename=ADMIN

这条记录,

则用admin登录后也不能再访问商户首页,

无权访问           

无需重启应用

六,查看spring boot的版本:

.   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v2.3.3.RELEASE)           
spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...
spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...
  • 新款SpringBoot在线教育平台开源了

  • 精品帖子大汇总

  • 一把“乐观锁”轻松搞定高并发下的幂等性问题(附视频教程)

  • 一文搞懂Java8 Lambda表达式(附视频教程)

感谢点赞支持下哈 

spring security 授予权限_spring boot:spring security用mysql实现动态权限管理(spring boot 2.3.3)...

继续阅读