码迷,mamicode.com
首页 > 数据库 > 详细

spring boot:spring security用mysql数据库实现RBAC权限管理(spring boot 2.3.1)

时间:2020-09-08 20:43:19      阅读:53      评论:0      收藏:0      [点我收藏+]

标签:dex   stack   pass   stat   格式   mamicode   except   森林   ast   

一,用数据库实现权限管理要注意哪些环节?

1,需要生成spring security中user类的派生类,用来保存用户id和昵称等信息,

    避免页面上显示用户昵称时需要查数据库

 

2,如果需要在页面上显示用户的登录信息,

   需要自定义一个interceptor,

   把用户的昵称等信息添加到 modelandview

 

3,普通用户的角色,即默认的权限,因为每个用户都具有,

   就不要写入到数据表中,

   避免数据量大时查询缓慢

 

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

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

说明:作者:刘宏缔 邮箱: 371125307@qq.com

 

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/securitylogin

 

2,项目功能说明

        演示了使用数据库实现的用户RBAC权限管理 

       三种页面:

        无权限限制页面:任何人都可访问

        需登录页面:修改密码等:登录才可以访问

       有权限限制页面:必须授予相应的角色后才能访问

 

3,项目结构:如图:

技术图片

技术图片

技术图片

 

三,配置文件说明

1,pom.xml

        <!--security begin-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--thymeleaf begin-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--validation begin-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!--mysql mybatis begin-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- JSON解析fastjson begin-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

 

2,application.properties

#thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=lhddemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper

#error
server.error.include-stacktrace=always
#log
logging.level.org.springframework.web=trace

 

3,数据库:

技术图片

 

 表结构:

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, 商户老张);

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

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);

 

四,java代码说明

1,WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public 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

    //指定加密的方式,避免出现:There is no PasswordEncoder mapped for the id "null"
    @Bean
    public PasswordEncoder passwordEncoder(){//密码加密类
        return  new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //static
        http.authorizeRequests()
                .antMatchers("/css/**","/js/**","/img/**")//静态资源等不需要验证
                .permitAll();
        //permitall
        http.authorizeRequests()
                .antMatchers("/home/**")//permitall
                .permitAll();
        //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()
                 .antMatchers("/admin/**").hasRole("ADMIN")
                 .antMatchers("/merchant/**").hasAnyRole("MERCHANT","ADMIN");

        //其他任何请求,登录后可以访问
        http.authorizeRequests().anyRequest().authenticated();
        //accessdenied
        http.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler);//无权限时的处理
        //user detail
        http.userDetailsService(secUserDetailService);
        //rememberme
        //图形验证码
        //http.csrf().disable();
  }

    @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,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中的信息,在这里扩展

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

 

3,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<GrantedAuthority> 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;
    }
}

 

4,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());
        }
    }
}

 

5,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));
    }
}

 

6,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,"登录成功"));
    }
}

 

7,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,"退出成功"));
    }
}

 

8,WebInterceptor.java

@Component
public 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","");
            }
        }
    }
}

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

 

9,login.html

<!DOCTYPE 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>
    <!-- CSRF -->
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <!-- default header name is X-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"> 首页 </a></li>
            </ul>
        </div><!--/.nav-collapse -->
    </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";
                } else {
                    alert("failed:"+data.msg);
                }
            },
            //调用执行后调用的函数
            complete: function(XMLHttpRequest, textStatus){
            },
            //调用出错执行的函数
            error: function(){
                //请求出错处理
                alert(error);
            }
        });
    }
</script>
</body>
</html>

 

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

 

五,测试效果

1,访问首页:

http://127.0.0.1:8080/home/home

未登录时:

技术图片

2,以普通用户lhd登录:

技术图片

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

无权访问

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

 

3,以merchant用户登录:

   role是MERCHANT

技术图片

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

无权访问

访问商户首页:可以访问

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

 

4,以admin用户登录:

role是ADMIN

技术图片

 

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

访问商户首页:可以访问

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

 

六,查看spring boot版本:

  .   ____          _            __ _ _
 /\\ / ___‘_ __ _ _(_)_ __  __ _ \ \ \ ( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  ‘  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

 

spring boot:spring security用mysql数据库实现RBAC权限管理(spring boot 2.3.1)

标签:dex   stack   pass   stat   格式   mamicode   except   森林   ast   

原文地址:https://www.cnblogs.com/architectforest/p/13576701.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!