<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.jpasecurity</groupId> <artifactId>SpringBootJpaSecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <!-- 声明项目配置依赖编码格式为 utf-8 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <fastjson.version>1.2.24</fastjson.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </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.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 添加MySQL依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> <version>8.0.13</version><!--$NO-MVN-MAN-VER$ --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--devtools热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>true</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
server.servlet.context-path=/ch7_1 spring.datasource.url=jdbc:mysql://localhost:3306/springbootjpa?serverTimezone=UTC&autoReconnect=true spring.datasource.username=root spring.datasource.password=admin spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.database=MYSQL spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jackson.serialization.indent-output=true spring.thymeleaf.cache=false logging.level.org.springframework.security=trace
package com.ch.ch7_1.entity; import java.io.Serializable; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @Entity @Table(name = "authority") @JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler", "fieldHandler" }) public class Authority { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(nullable = false) private String name; @ManyToMany(mappedBy = "authorityList") @JsonIgnore private List<MyUser> userList; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<MyUser> getUserList() { return userList; } public void setUserList(List<MyUser> userList) { this.userList = userList; } }
package com.ch.ch7_1.entity; import java.io.Serializable; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import javax.persistence.Transient; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @Entity @Table(name = "user") @JsonIgnoreProperties(value = { "hibernateLazyInitializer","handler","fieldHandler" }) public class MyUser implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String username; private String password; // 这里不能是懒加载lazy,否则在MyUserSecurityService的loadUserByUsername方法中获得不到权限 @ManyToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.EAGER) @JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "authority_id")) private List<Authority> authorityList; // repassword不映射到数据表 @Transient private String repassword; public int getId() { return id; } public void setId(int id) { this.id = id; } 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<Authority> getAuthorityList() { return authorityList; } public void setAuthorityList(List<Authority> authorityList) { this.authorityList = authorityList; } public String getRepassword() { return repassword; } public void setRepassword(String repassword) { this.repassword = repassword; } }
package com.ch.ch7_1.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.ch.ch7_1.entity.MyUser; public interface MyUserRepository extends JpaRepository<MyUser, Integer> { // 根据用户名查询用户,方法名命名符合Spring Data JPA规范 MyUser findByUsername(String username); }
package com.ch.ch7_1.service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.ui.Model; import com.ch.ch7_1.entity.MyUser; public interface UserService { public String register(MyUser userDomain); public String loginSuccess(Model model); public String main(Model model); public String deniedAccess(Model model); public String logout(HttpServletRequest request, HttpServletResponse response); }
package com.ch.ch7_1.service; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.ch.ch7_1.entity.Authority; import com.ch.ch7_1.entity.MyUser; import com.ch.ch7_1.repository.MyUserRepository; @Service public class UserServiceImpl implements UserService { @Autowired private MyUserRepository myUserRepository; /** * 实现注册 */ @Override public String register(MyUser userDomain) { String username = userDomain.getUsername(); List<Authority> authorityList = new ArrayList<Authority>(); // 管理员权限 if ("admin".equals(username)) { Authority a1 = new Authority(); Authority a2 = new Authority(); a1.setId(1); a1.setName("ROLE_ADMIN"); a2.setId(2); a2.setName("ROLE_DBA"); authorityList.add(a1); authorityList.add(a2); } else {// 用户权限 Authority a1 = new Authority(); a1.setId(3); a1.setName("ROLE_USER"); authorityList.add(a1); } userDomain.setAuthorityList(authorityList); // 加密密码 String secret = new BCryptPasswordEncoder().encode(userDomain.getPassword()); userDomain.setPassword(secret); MyUser mu = myUserRepository.save(userDomain); if (mu != null)// 注册成功 return "/login"; return "/register";// 注册失败 } /** * 用户登录成功 */ @Override public String loginSuccess(Model model) { model.addAttribute("user", getUname()); model.addAttribute("role", getAuthorities()); return "/user/loginSuccess"; } /** * 管理员登录成功 */ @Override public String main(Model model) { model.addAttribute("user", getUname()); model.addAttribute("role", getAuthorities()); return "/admin/main"; } /** * 注销用户 */ @Override public String logout(HttpServletRequest request, HttpServletResponse response) { // 获得用户认证信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { // 注销 new SecurityContextLogoutHandler().logout(request, response, authentication); } return "redirect:/login?logout"; } /** * 没有权限拒绝访问 */ @Override public String deniedAccess(Model model) { model.addAttribute("user", getUname()); model.addAttribute("role", getAuthorities()); return "deniedAccess"; } /** * 获得当前用户名称 */ private String getUname() { return SecurityContextHolder.getContext().getAuthentication().getName(); } /** * 获得当前用户权限 */ private String getAuthorities() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); List<String> roles = new ArrayList<String>(); for (GrantedAuthority ga : authentication.getAuthorities()) { roles.add(ga.getAuthority()); } return roles.toString(); } }
package com.ch.ch7_1.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.ch.ch7_1.entity.MyUser; import com.ch.ch7_1.service.UserService; @Controller public class TestSecurityController { @Autowired private UserService userService; @RequestMapping("/") public String index() { return "/index"; } @RequestMapping("/toLogin") public String toLogin() { return "/login"; } @RequestMapping("/toRegister") public String toRegister(@ModelAttribute("userDomain") MyUser userDomain) { return "/register"; } @RequestMapping("/register") public String register(@ModelAttribute("userDomain") MyUser userDomain) { return userService.register(userDomain); } @RequestMapping("/login") public String login() { // 这里什么都不错,由Spring Security负责登录验证 return "/login"; } @RequestMapping("/user/loginSuccess") public String loginSuccess(Model model) { return userService.loginSuccess(model); } @RequestMapping("/admin/main") public String main(Model model) { return userService.main(model); } @RequestMapping("/logout") public String logout(HttpServletRequest request, HttpServletResponse response) { return userService.logout(request, response); } @RequestMapping("/deniedAccess") public String deniedAccess(Model model) { return userService.deniedAccess(model); } }
package com.ch.ch7_1.security; 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.ch.ch7_1.entity.Authority; import com.ch.ch7_1.entity.MyUser; import com.ch.ch7_1.repository.MyUserRepository; /** * 获得对应的UserDetails,保存与认证相关的信息 */ @Service public class MyUserSecurityService implements UserDetailsService { @Autowired private MyUserRepository myUserRepository; /** * 通过重写loadUserByUsername方法查询对应的用户 UserDetails是Spring Security的一个核心接口 * UserDetails定义了可以获取用户名、密码、权限等与认证相关信息的方法 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根据用户名(页面接收的用户名)查询当前用户 MyUser myUser = myUserRepository.findByUsername(username); if (myUser == null) { throw new UsernameNotFoundException("用户名不存在"); } // GrantedAuthority代表赋予当前用户的权限(认证权限) List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); // 获得当前用户权限集合 List<Authority> roles = myUser.getAuthorityList(); // 将当前用户的权限保存为用户的认证权限 for (Authority authority : roles) { GrantedAuthority sg = new SimpleGrantedAuthority(authority.getName()); authorities.add(sg); } // org.springframework.security.core.userdetails.User是Spring Security的内部实现, // 专门用于保存用户名、密码、权限等与认证相关的信息 User su = new User(myUser.getUsername(), myUser.getPassword(), authorities); return su; } }
package com.ch.ch7_1.security; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.servlet.ServletException; 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 MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { // Spring Security的重定向策略 private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); /** * 重写handle方法,通过RedirectStrategy重定向到指定的URL */ @Override protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 根据当前认证用户的角色返回适当的URL String tagetURL = getTargetURL(authentication); // 重定向到指定的URL redirectStrategy.sendRedirect(request, response, tagetURL); } /** * 从Authentication对象中提取当前登录用户的角色,并根据其角色返回适当的URL */ protected String getTargetURL(Authentication authentication) { String url = ""; // 获得当前登录用户的权限(角色)集合 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); List<String> roles = new ArrayList<String>(); // 将权限(角色)名称添加到List集合 for (GrantedAuthority au : authorities) { roles.add(au.getAuthority()); } // 判断不同角色的用户跳转到不同的URL // 这里的URL是控制器的请求匹配路径 if (roles.contains("ROLE_USER")) { url = "/user/loginSuccess"; } else if (roles.contains("ROLE_ADMIN")) { url = "/admin/main"; } else { url = "/deniedAccess"; } return url; } }
package com.ch.ch7_1.security; 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; /** * 认证和授权处理类 */ @Configuration public class MySecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { // 依赖注入通用的用户服务类 @Autowired private MyUserSecurityService myUserSecurityService; // 依赖注入加密接口 @Autowired private PasswordEncoder passwordEncoder; // 依赖注入用户认证接口 @Autowired private AuthenticationProvider authenticationProvider; // 依赖注入认证处理成功类,验证用户成功后处理不同用户跳转到不同的页面 @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; /** * BCryptPasswordEncoder是PasswordEncoder的接口实现 实现加密功能 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * DaoAuthenticationProvider是AuthenticationProvider的实现 */ @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provide = new DaoAuthenticationProvider(); // 不隐藏用户未找到异常 provide.setHideUserNotFoundExceptions(false); // 设置自定义认证方式,用户登录认证 provide.setUserDetailsService(myUserSecurityService); // 设置密码加密程序认证 provide.setPasswordEncoder(passwordEncoder); return provide; } /** * 用户认证 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { System.out.println("configure(AuthenticationManagerBuilder auth) "); // 设置认证方式 auth.authenticationProvider(authenticationProvider); } /** * 请求授权 用户授权操作 */ @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("configure(HttpSecurity http)"); http.authorizeRequests() // 首页、登录、注册页面、登录注册功能、以及静态资源过滤掉,即可任意访问 .antMatchers("/toLogin", "/toRegister", "/", "/login", "/register", "/css/**", "/fonts/**", "/js/**") .permitAll() // 这里默认追加ROLE_,/user/**是控制器的请求匹配路径 .antMatchers("/user/**").hasRole("USER").antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA") // 其他所有请求登录后才能访问 .anyRequest().authenticated().and() // 将输入的用户名与密码和授权的进行比较 .formLogin().loginPage("/login").successHandler(myAuthenticationSuccessHandler) .usernameParameter("username").passwordParameter("password") // 登录失败 .failureUrl("/login?error").and() // 注销行为可任意访问 .logout().permitAll().and() // 指定异常处理页面 .exceptionHandling().accessDeniedPage("/deniedAccess"); } }
package com.ch.ch7_1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Ch71Application { public static void main(String[] args) { SpringApplication.run(Ch71Application.class, args); } }
启动项目,后台打印下面截图这个提示,就表示自定义的用户认证和用户授权工作已经生效。
http://localhost:8080/ch7_1/login
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" /> </head> <body> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">拒绝访问页面</h3> </div> </div> <div class="container"> <div> <h3><span th:text="${user}"></span>您没有权限访问该页面!您的权限是您的权限是<span th:text="${role}"></span>。</h3><br><br> <a th:href="@{/logout}">安全退出</a> </div> </div> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" /> </head> <body> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Spring Security测试首页</h3> </div> </div> <div class="container"> <div> <a th:href="@{/toLogin}">去登录</a><br><br> <a th:href="@{/toRegister}">去注册</a> </div> </div> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登录页面</title> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" /> <script type="text/javascript" th:src="@{js/jquery.min.js}"></script> <script type="text/javascript"> $(function(){ $("#loginBtn").click(function(){ var username = $("#username"); var password = $("#password"); var msg = ""; if(username.val() == ""){ msg = "用户名不能为空!"; username.focus(); }else if(password.val() == ""){ msg = "密码不能为空!"; password.focus(); } if(msg != ""){ alert(msg); return false; } $("#myform").submit(); }); }); </script> <body> <div class="container"> <div class="bg-primary" style="width:100%; height: 70px;padding-top: 10px;"> <h2 align="center">用户登录</h2> </div> <br> <br> <form th:action="@{/login}" id="myform" method="post" class="form-horizontal" role="form" > <!-- 用户名或密码错误 --> <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="form-group has-success"> <label class="col-sm-2 col-md-2 control-label">用户名</label> <div class="col-sm-4 col-md-4"> <input type="text" class="form-control" placeholder="请输入您的用户名" name="username" id="username"/> </div> </div> <div class="form-group has-success"> <label class="col-sm-2 col-md-2 control-label">密码</label> <div class="col-sm-4 col-md-4"> <input type="password" class="form-control" placeholder="请输入您的密码" name="password" id="password"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="button" id="loginBtn" class="btn btn-success" >登录</button> <button type="reset" class="btn btn-primary" >重置</button> </div> </div> </form> </div> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>注册页面</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" /> <script th:src="@{/js/jquery.min.js}"></script> <script type="text/javascript" th:inline="javascript"> function checkBpwd(){ if($("#username").val() == ""){ alert("用户名必须输入!"); $("#username").focus(); return false; }else if($("#password").val() == ""){ alert("密码必须输入!"); $("#password").focus(); return false; }else if($("#password").val() != $("#repassword").val()){ alert("两次密码不一致!"); $("#password").focus(); return false; }else{ document.myform.submit(); } } </script> <body> <div class="container"> <div class="bg-primary" style="width:100%; height: 70px;padding-top: 10px;"> <h2 align="center">用户注册</h2> </div> <br> <br> <form th:action="@{/register}" name="myform" method="post" th:object="${userDomain}" class="form-horizontal" role="form" > <div class="form-group has-success"> <label class="col-sm-2 col-md-2 control-label">用户名</label> <div class="col-sm-4 col-md-4"> <input type="text" class="form-control" placeholder="请输入您的用户名" th:field="*{username}"/> </div> </div> <div class="form-group has-success"> <label class="col-sm-2 col-md-2 control-label">密码</label> <div class="col-sm-4 col-md-4"> <input type="password" class="form-control" placeholder="请输入您的密码" th:field="*{password}" /> </div> </div> <div class="form-group has-success"> <label class="col-sm-2 col-md-2 control-label">确认密码</label> <div class="col-sm-4 col-md-4"> <input type="password" class="form-control" placeholder="请输入您的密码" th:field="*{repassword}"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="button" onclick="checkBpwd()" class="btn btn-success" >注册</button> <button type="reset" class="btn btn-primary" >重置</button> </div> </div> </form> </div> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" /> </head> <body> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">登录成功页面</h3> </div> </div> <div class="container"> <div> <h3>欢迎<span th:text="${user}"></span>登录成功!您的权限是<span th:text="${role}"></span>。</h3><br><br> <a th:href="@{/admin/main}">去访问管理员页面</a><br><br> <a th:href="@{/logout}">安全退出</a> </div> </div> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" /> </head> <body> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">管理员页面</h3> </div> </div> <div class="container"> <div> <h3>欢迎<span th:text="${user}"></span>访问管理员页面!您的权限是<span th:text="${role}"></span>。</h3><br><br> <a th:href="@{/user/loginSuccess}">去访问用户登录成功页面</a><br><br> <a th:href="@{/logout}">安全退出</a> </div> </div> </body> </html>