基于MyBatis的Spring Boot Security实战

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/chengqiuming/article/details/102649890

一 pom

<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>org.fkit</groupId>
     <artifactId>securitymybatistest</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <packaging>jar</packaging>
     <name>securitymybatistest</name>
     <url>http://maven.apache.org</url>
     <!-- spring-boot-starter-parent是Spring Boot的核心启动器,  包含了自动配置、日志和YAML等大量默认的配置,大大简化了我们的开发。
           引入之后相关的starter引入就不需要添加version配置, spring  boot会自动选择最合适的版本进行添加。 -->
     <parent>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-parent</artifactId>
           <version>2.0.0.RELEASE</version>
           <relativePath />
     </parent>
     <properties>
           <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
           <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
           <java.version>1.8</java.version>
     </properties>
     <dependencies>
           <!-- 添加spring-boot-starter-web模块依赖 -->
           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
           <!-- 添加spring-boot-starter-thymeleaf模块依赖 -->
           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
           </dependency>
           <!-- 添加spring-boot-starter-security 依赖 -->
           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
           </dependency>
           <!-- 添加mysql数据库驱动 -->
           <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
           </dependency>
           <!-- 添加MyBatis依赖 -->
           <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
           </dependency>
           <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
           </dependency>
     </dependencies>
</project>

二 启动类

package org.fkit.securitymybatistest;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


// @SpringBootApplication指定这是一个 spring boot的应用程序.
@SpringBootApplication
// 扫描数据访问层接口的包名。
@MapperScan("org.fkit.securitymybatistest.mapper")
public class App {
    public static void main(String[] args) {
        // SpringApplication 用于从main方法启动Spring应用的类。
        SpringApplication.run(App.class, args);
    }
}

三 控制器

package org.fkit.securitytest.controller;


import java.util.ArrayList;
import java.util.List;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class AppController {


    @RequestMapping("/")
    public String index() {
        return "index";
    }


    @RequestMapping(value = "/login")
    public String login() {
        return "login";
    }


    @RequestMapping("/home")
    public String homePage(Model model) {
        model.addAttribute("user", getUsername());
        model.addAttribute("role", getAuthority());
        return "home";
    }


    @RequestMapping(value = "/admin")
    public String adminPage(Model model) {
        model.addAttribute("user", getUsername());
        model.addAttribute("role", getAuthority());
        return "admin";
    }


    @RequestMapping(value = "/dba")
    public String dbaPage(Model model) {
        model.addAttribute("user", getUsername());
        model.addAttribute("role", getAuthority());
        return "dba";
    }


    @RequestMapping(value = "/accessDenied")
    public String accessDeniedPage(Model model) {
        model.addAttribute("user", getUsername());
        model.addAttribute("role", getAuthority());
        return "accessDenied";
    }


    @RequestMapping(value = "/logout")
    public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
        // Authentication是一个接口,表示用户认证信息
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // 如果用户认知信息不为空,注销
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        // 重定向到login页面
        return "redirect:/login?logout";
    }


    /**
     * 获得当前用户名称
     */
    private String getUsername() {
        // 从SecurityContex中获得Authentication对象代表当前用户的信息
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        System.out.println("username = " + username);
        return username;
    }


    /**
     * 获得当前用户权限
     */
    private String getAuthority() {
        // 获得Authentication对象,表示用户认证信息。
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        List<String> roles = new ArrayList<String>();
        // 将角色名称添加到List集合
        for (GrantedAuthority a : authentication.getAuthorities()) {
            roles.add(a.getAuthority());
        }
        System.out.println("role = " + roles);
        return roles.toString();
    }


}

四 数据访问接口

package org.fkit.securitymybatistest.mapper;

import java.util.List;


import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;
import org.fkit.securitymybatistest.pojo.FKRole;
import org.fkit.securitymybatistest.pojo.FKUser;


public interface UserMapper {
    
    // 根据loginName查询用户信息,同时关联查询出用户的权限
    @Select("select * from tb_user where login_name = #{loginName}")
     @Results({  
            @Result(id=true,column="id",property="id"),  
            @Result(column="login_name",property="loginName"),  
            @Result(column="password",property="password"),  
            @Result(column="username",property="username"),  
            @Result(column="id",property="roles",
            many=@Many(select="findRoleByUser",
            fetchType=FetchType.EAGER))  
         })  
    FKUser findByLoginName(String loginName);
    
    // 根据用户id关联查询用户的所有权限
    @Select(" SELECT id,authority FROM tb_role r,tb_user_role ur "
            + " WHERE r.id = ur.role_id AND user_id = #{id}")
    List<FKRole> findRoleByUser(Long id);

}

五 创建自定义服务类

package org.fkit.securitymybatistest.service;


import java.util.ArrayList;
import java.util.List;


import org.fkit.securitymybatistest.mapper.UserMapper;
import org.fkit.securitymybatistest.pojo.FKRole;
import org.fkit.securitymybatistest.pojo.FKUser;
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;


/**
* 需要实现UserDetailsService接口
* 因为在Spring Security中我们配置相关参数需要UserDetailsService类型的数据
* */
@Service
public class UserService implements UserDetailsService{


    // 注入持久层接口UserMapper
    @Autowired
    UserMapper userMapper;
    
    // 实现接口中的loadUserByUsername方法,通过该方法查询到对应的用户
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用持久层接口findByLoginName方法查找用户,此处的传进来的参数实际是loginName
        FKUser fkUser = userMapper.findByLoginName(username);
//        System.out.println("user = " + fkUser);
        if (fkUser == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 创建List集合,用来保存用户权限,GrantedAuthority对象代表赋予给当前用户的权限
        List<GrantedAuthority> authorities = new ArrayList<>();
        // 获得当前用户权限集合
        List<FKRole> roles = fkUser.getRoles();
        for (FKRole role : roles) {
            // 将关联对象Role的authority属性保存为用户的认证权限
            authorities.add(new SimpleGrantedAuthority(role.getAuthority()));
        }
        // 此处返回的是org.springframework.security.core.userdetails.User类,该类是Spring Security内部的实现
        return new User(fkUser.getUsername(), fkUser.getPassword(), authorities);
    }
}

六 认证处理类

1 AppAuthenticationSuccessHandler

package org.fkit.securitytest.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import  org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
@Component
public class AppAuthenticationSuccessHandler extends  SimpleUrlAuthenticationSuccessHandler {
     // Spring Security 通过RedirectStrategy对象负责所有重定向事务
     private RedirectStrategy redirectStrategy = new  DefaultRedirectStrategy();
     /*
      * 重写handle方法,方法中通过RedirectStrategy对象重定向到指定的url
      */
     @Override
     protected void handle(HttpServletRequest request,  HttpServletResponse response, Authentication authentication)
                throws IOException {
           // 通过determineTargetUrl方法返回需要跳转的url
           String targetUrl =  determineTargetUrl(authentication);
           // 重定向请求到指定的url
           redirectStrategy.sendRedirect(request, response,  targetUrl);
     }
     /*
      * 从Authentication对象中提取角色提取当前登录用户的角色,并根据其角色返回适当的URL。
      */
     protected String determineTargetUrl(Authentication  authentication) {
           String url = "";
           // 获取当前登录用户的角色权限集合
           Collection<? extends GrantedAuthority> authorities =  authentication.getAuthorities();
           List<String> roles = new ArrayList<String>();
           // 将角色名称添加到List集合
           for (GrantedAuthority a : authorities) {
                roles.add(a.getAuthority());
           }
           // 判断不同角色跳转到不同的url
           if (isAdmin(roles)) {
                url = "/admin";
           } else if (isUser(roles)) {
                url = "/home";
           } else {
                url = "/accessDenied";
           }
           System.out.println("url = " + url);
           return url;
     }
     private boolean isUser(List<String> roles) {
           if (roles.contains("ROLE_USER")) {
                return true;
           }
           return false;
     }
     private boolean isAdmin(List<String> roles) {
           if (roles.contains("ROLE_ADMIN")) {
                return true;
           }
           return false;
     }
     public void setRedirectStrategy(RedirectStrategy  redirectStrategy) {
           this.redirectStrategy = redirectStrategy;
     }
     protected RedirectStrategy getRedirectStrategy() {
           return redirectStrategy;
     }
}

2 AppSecurityConfigurer

package org.fkit.securityjpatest.security;


import org.fkit.securityjpatest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


/**
* 自定义Spring Security认证处理类的时候 我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应
* 方法即可。
*/
@Configuration
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter {


    // 依赖注入用户服务类
    @Autowired
    private UserService userService;


    // 依赖注入加密接口
    @Autowired
    private PasswordEncoder passwordEncoder;


    // 依赖注入用户认证接口
    @Autowired
    private AuthenticationProvider authenticationProvider;


    // 依赖注入认证处理成功类,验证用户成功后处理不同用户跳转到不同的页面
    @Autowired
    AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;


    /*
     * BCryptPasswordEncoder是Spring Security提供的PasswordEncoder接口是实现类
     * 用来创建密码的加密程序,避免明文存储密码到数据库
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    // DaoAuthenticationProvider是Spring Security提供AuthenticationProvider的实现
    @Bean
    public AuthenticationProvider authenticationProvider() {
        // 创建DaoAuthenticationProvider对象
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 不要隐藏"用户未找到"的异常
        provider.setHideUserNotFoundExceptions(false);
        // 通过重写configure方法添加自定义的认证方式。
        provider.setUserDetailsService(userService);
        // 设置密码加密程序认证
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("AppSecurityConfigurer configure auth......");
        // 设置认证方式。
        auth.authenticationProvider(authenticationProvider);


    }


    /**
     * 设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,注销请求也是任何人都可以访问的。
     * permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("AppSecurityConfigurer configure http......");
        http.authorizeRequests()
                // spring-security 5.0 之后需要过滤静态资源
                .antMatchers("/login", "/css/**", "/js/**", "/img/*").permitAll().antMatchers("/", "/home")
                .hasRole("USER").antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA").anyRequest().authenticated().and()
                .formLogin().loginPage("/login").successHandler(appAuthenticationSuccessHandler)
                .usernameParameter("loginName").passwordParameter("password").and().logout().permitAll().and()
                .exceptionHandling().accessDeniedPage("/accessDenied");
    }


}

七 视图

1 login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:th="http://www.thymeleaf.org"
     xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<title>Spring Boot Security示例</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{css/app.css}" />
<link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"  />
<link rel="stylesheet" type="text/css"
     href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" />
<script type="text/javascript"  th:src="@{js/jquery-1.11.0.min.js}"></script>
<script type="text/javascript"  th:src="@{js/bootstrap.min.js}"></script>
<script type="text/javascript">
     $(function() {
           $("#loginBtn").click(function() {
                var loginName = $("#loginName");
                var password = $("#password");
                var msg = "";
                if (loginName.val() == "") {
                     msg = "登录名称不能为空!";
                     loginName.focus();
                } else if (password.val() == "") {
                     msg = "密码不能为空!";
                     password.focus();
                }
                if (msg != "") {
                     alert(msg);
                     return false;
                }
                $("#loginForm").submit();
           });
     });
</script>
</head>
<body>
     <div class="panel panel-primary">
           <div class="panel-heading">
                <h3 class="panel-title">简单Spring Boot  Security示例</h3>
           </div>
     </div>
     <div id="mainWrapper">
           <div class="login-container">
                <div class="login-card">
                     <div class="login-form">
                           <!-- 表单提交到login -->
                           <form id="loginForm"  th:action="@{/login}" method="post"
                                class="form-horizontal">
                                <!-- 用户名或密码错误提示 -->
                                <div th:if="${param.error !=  null}">
                                     <div class="alert  alert-danger">
                                           <p>
                                                <font  color="red">用户名或密码错误!</font>
                                           </p>
                                     </div>
                                </div>
                                <!-- 注销提示 -->
                                <div th:if="${param.logout !=  null}">
                                     <div class="alert  alert-success">
                                           <p>
                                                <font  color="red">用户已注销成功!</font>
                                           </p>
                                     </div>
                                </div>
                                <div class="input-group  input-sm">
                                     <label  class="input-group-addon"><i class="fa fa-user"></i></label>
                                     <input type="text"  class="form-control" id="loginName"
                                           name="loginName"  placeholder="请输入用户名" />
                                </div>
                                <div class="input-group  input-sm">
                                     <label  class="input-group-addon"><i class="fa fa-lock"></i></label>
                                     <input type="password"  class="form-control" id="password"
                                           name="password"  placeholder="请输入密码" />
                                </div>
                                <div class="form-actions">
                                     <input id="loginBtn"  type="button"
                                           class="btn btn-block  btn-primary btn-default" value="登录" />
                                </div>
                           </form>
                     </div>
                </div>
           </div>
     </div>
</body>
</html>

2 home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>home页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"  />
<script type="text/javascript"  th:src="@{js/jquery-1.11.0.min.js}"></script>
<script type="text/javascript"  th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
     <div class="panel panel-primary">
           <div class="panel-heading">
                <h3 class="panel-title">Home页面</h3>
           </div>
     </div>
     <h3>
           欢迎[<font color="red"><span th:text="${user}">用户名</span></font>]访问Home页面!
           您的权限是<font color="red"><span th:text="${role}">权限</span></font><br />
           <br /> <a href="admin">访问admin页面</a><br />
           <br /> <a href="logout">安全退出</a>
     </h3>
</body>
</html>

3 admin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>admin页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"  />
<script type="text/javascript"  th:src="@{js/jquery-1.11.0.min.js}"></script>
<script type="text/javascript"  th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
     <div class="panel panel-primary">
           <div class="panel-heading">
                <h3 class="panel-title">Admin页面</h3>
           </div>
     </div>
     <h3>
           欢迎[<font color="red"><span th:text="${user}">用户名</span></font>]访问Admin页面!
           您的权限是<font color="red"><span th:text="${role}">权限</span></font><br />
           <br /> <a href="dba">访问dba页面</a><br />
           <br /> <a href="logout">安全退出</a>
     </h3>
</body>
</html>

4 dba.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>dba页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"  />
<script type="text/javascript"  th:src="@{js/jquery-1.11.0.min.js}"></script>
<script type="text/javascript"  th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
     <div class="panel panel-primary">
           <div class="panel-heading">
                <h3 class="panel-title">DBA页面</h3>
           </div>
     </div>
     <h3>
           欢迎[<font color="red"><span th:text="${user}">用户名</span></font>]访问访问DBA页面!
           您的权限是<font color="red"><span th:text="${role}">权限</span></font><br />
           <br /> <a href="logout">安全退出</a>
     </h3>
</body>
</html>

5 accessDenied.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>访问拒绝页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"  />
<script type="text/javascript"  th:src="@{js/jquery-1.11.0.min.js}"></script>
<script type="text/javascript"  th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
     <div class="panel panel-primary">
           <div class="panel-heading">
                <h3 class="panel-title">AccessDenied页面</h3>
           </div>
     </div>
     <h3>
           <font color="red"><span th:text="${user}">用户名</span></font>,
           您没有权限访问页面! 您的权限是<font color="red"><span  th:text="${role}">权限</span></font><br />
           <br /> <a href="logout">安全退出</a>
     </h3>
</body>
</html>

八 创建持久化类

1 FKRole

package org.fkit.securitymybatistest.pojo;
import java.io.Serializable;
public class FKRole implements Serializable {
     private static final long serialVersionUID = 1L;
     
     private Long id;
    private String authority;
    
     public FKRole() {
           super();
           // TODO Auto-generated constructor stub
     }
     public Long getId() {
           return id;
     }
     public void setId(Long id) {
           this.id = id;
     }
     
     public String getAuthority() {
           return authority;
     }
     public void setAuthority(String authority) {
           this.authority = authority;
     }
     @Override
     public String toString() {
           return "FKRole [id=" + id + ", authority=" +  authority + "]";
     }
}

2 FKUser

package org.fkit.securitymybatistest.pojo;


import java.io.Serializable;
import java.util.List;


public class FKUser implements Serializable{
    
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String loginName;
    private String username;
    private String password;


    private List<FKRole> roles;


    public Long getId() {
        return id;
    }


    public void setId(Long id) {
        this.id = id;
    }


    public String getLoginName() {
        return loginName;
    }


    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }


    public String getUsername() {
        return username;
    }


    public void setUsername(String username) {
        this.username = username;
    }


    public String getPassword() {
        return password;
    }


    public void setPassword(String password) {
        this.password = password;
    }


    public List<FKRole> getRoles() {
        return roles;
    }


    public void setRoles(List<FKRole> roles) {
        this.roles = roles;
    }


    @Override
    public String toString() {
        return "FKUser [id=" + id + ", loginName=" + loginName + ", username=" + username + ", password=" + password
                + ", roles=" + roles + "]";
    }
}

九 创建数据库Springboot

相关数据表如下:

十 配置文件

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=
logging.level.org.springframework.security=info
logging.level.org.fkit.securitymybatistest.mapper.UserMapper=debug
spring.thymeleaf.cache=false

十一 测试

猜你喜欢

转载自blog.csdn.net/chengqiuming/article/details/102649890