集成spring security实现登录权限资源控制
项目结构,标准的springboot项目结构
配置pom文件引入相关jar
<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.zzp</groupId>
<artifactId>my-spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tomato-study</name>
<description>学习spring security</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml配置
server:
port: 8088
spring:
thymeleaf:
cache: false
datasource:
url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=utf8mb4
driver-class-name: org.mariadb.jdbc.Driver
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
database-platform: org.hibernate.dialect.MySQL5Dialect
logging:
file: spring.log
level:
root: INFO
配置security 新建一个类WebSecurityConfig集成 WebSecurityConfigurerAdapter
package com.zzp.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.zzp.authentication.MyAuthenticationFailHandler;
import com.zzp.authentication.MyAuthenticationSuccessHandler;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
/**
* 匹配 "/" 路径,不需要权限即可访问
* 匹配 "/user" 及其以下所有路径,都需要 "USER" 权限
* 登录地址为 "/login",登录成功默认跳转到页面 "/user"
* 退出登录的地址为 "/logout",退出成功后跳转到页面 "/login"
* 默认启用 CSRF这是跨域设置
*/
/**
* 注入 自定义的 登录成功处理类
*/
@Autowired
private MyAuthenticationSuccessHandler mySuccessHandler;
@Autowired
private MyAuthenticationFailHandler myFailHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/user/**").hasAuthority("USER")
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/loginUser")
.successForwardUrl("/user")
.successHandler(mySuccessHandler)
.failureHandler(myFailHandler)
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login");
// .and()
// .csrf().disable();
//这里不用自定义的认证过滤
//http.addFilterAt(customFromLoginFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* 自定义认证过滤器
*/
private CustomFromLoginFilter customFromLoginFilter() {
return new CustomFromLoginFilter("/login");
}
}
新建两个登录成功和失败处理类
package com.zzp.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录失败处理器
* Created by Fant.J.
*/
@Component("myAuthenctiationFailureHandler")
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
/**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(getClass());
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
System.out.println("----HHH---->>"+e.getMessage());
super.setDefaultFailureUrl("/login?error=true");
super.onAuthenticationFailure(request,response,e);
}
}
package com.zzp.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录成功处理类
* Created by Fant.J.
*/
@Component("myAuthenctiationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
/**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(getClass());
// private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
super.setDefaultTargetUrl("/user");
super.onAuthenticationSuccess(request, response, authentication);
//redirectStrategy.sendRedirect(request, response, "/user");
}
}
新增一个类实现接口UserDetailsService,这个是登录认证处理相关的。
package com.zzp.config;
import com.zzp.entity.UserDO;
import com.zzp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class DbUserDetailsService implements UserDetailsService {
private final UserService userService;
/**
* 重写PasswordEncoder 接口中的方法,实例化加密策略
* @return 返回 BCrypt 加密策略
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
private PasswordEncoder myPasswordEncoder;
@Autowired
DbUserDetailsService(UserService userService){
this.userService = userService;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDO userDO = userService.getByUsername(username);
if (userDO == null){
System.out.println("=========用户不存在!=========>>");
throw new UsernameNotFoundException("用户不存在!");
}
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("USER"));
String password = userDO.getPassword();
password = myPasswordEncoder.encode(password);
System.out.println("=========loadUserByUsername=========>>");
return new org.springframework.security.core.userdetails.User(userDO.getUsername(), password, simpleGrantedAuthorities);
}
}
Controller编写
homeController
@Controller
public class HomeController {
@GetMapping({"/", "/index", "/home"})
public String root(){
return "index";
}
@GetMapping("/login")
public String login(){
return "login";
}
}
UserController
@Controller
public class UserController {
@RequestMapping("/user")
public String user(@AuthenticationPrincipal Principal principal, Model model){
System.out.println("========user=======>>");
model.addAttribute("username", principal.getName());
return "user/user";
}
}
user实体类
package com.zzp.entity;
import javax.persistence.*;
@Entity
@Table(name = "user")
public class UserDO {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
private String nickname;
public Long getId() {
return id;
}
public void setId(Long 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 String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
Repository 就是dao 继承CrudRepository
package com.zzp.repository;
import com.zzp.entity.UserDO;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository<UserDO, Long> {
UserDO findByUsername(String username);
}
service
public interface UserService {
/**
* 添加新用户
*
* username 唯一, 默认 USER 权限
*/
void insert(UserDO userDO);
/**
* 查询用户信息
* @param username 账号
* @return UserEntity
*/
UserDO getByUsername(String username);
}
@Service
@Primary
@Slf4j
public class BaseUserService implements UserService {
private final UserRepository userRepository;
public BaseUserService(UserRepository userRepository){
this.userRepository = userRepository;
}
public void insert(UserDO userDO) {
String username = userDO.getUsername();
if (exist(username)){
throw new RuntimeException("用户名已存在!");
}
userRepository.save(userDO);
}
public UserDO getByUsername(String username) {
return userRepository.findByUsername(username);
}
/**
* 判断用户是否存在
*/
private boolean exist(String username){
UserDO userDO = userRepository.findByUsername(username);
return (userDO != null);
}
}
前端页面
user.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>用户中心 | Spring Security Demos</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
</head>
<body style="background-color: #f1f1f1; padding-bottom: 0">
<div th:insert="~{header :: nav}"></div>
<div class="container" style="margin-top: 60px">
<div style="text-align: center; margin-top: 10%">
<img src="http://upload.jianshu.io/users/upload_avatars/3424642/fb55f16faaf6.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240" alt="avatar" class="img-circle" style="margin: 0 auto">
<p th:text="${username}" style="margin-top: 25px; font-size: 20; color: crimson">zzp</p>
<form th:action="@{/logout}" method="post">
<button class="btn btn-danger" style="margin-top: 20px">退出登录</button>
</form>
</div>
</div>
</body>
</html>
head.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
</head>
<body>
<div th:fragment="nav">
<nav class="navbar navbar-inverse navbar-fixed-top" style="margin: 0; border: 0">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a href="/">首页</a>
</li>
<li>
<a href="https://anoyi.com/" target="_blank">博客</a>
</li>
<li>
<a href="http://www.jianshu.com/u/7b7ec6f2db21" target="_blank">简书</a>
</li>
<li>
<a href="https://github.com/ChinaSilence" target="_blank">Github</a>
</li>
<li>
<a href="http://spring4all.com" target="_blank">源码</a>
</li>
</ul>
<div class="navbar-form navbar-right">
<a class="btn btn-danger" th:href="@{/logout}" th:if="${#httpServletRequest.remoteUser}">退出登录</a>
<a class="btn btn-success" th:href="@{/login}" th:unless="${#httpServletRequest.remoteUser}">登录</a>
</div>
</div>
</div>
</nav>
</div>
</body>
</html>
index.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Security Demos</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
</head>
<body style="padding-bottom: 0">
<div th:insert="~{header :: nav}"></div>
<div class="jumbotron">
<div class="container" style="padding-top: 30px">
<h1>Spring Security</h1>
<p style="margin-top: 20px;">Spring Security 是一个功能强大且可高度自定义的身份验证和访问控制框架,它是保护基于 Spring 的应用的最佳实践,与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义要求。</p>
<p style="margin-top: 20px;"><a class="btn btn-primary btn-lg" href="https://spring.io/projects/spring-security" role="button"> 官方文档 »</a></p>
</div>
</div>
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-4">
<h2>Github</h2>
<p>Spring Security 源码</p>
<p><a class="btn btn-default" href="https://github.com/spring-projects/spring-security" role="button">查看详情 »</a></p>
</div>
<div class="col-md-4">
<h2>Stackoverflow</h2>
<p>Spring Security 相关问题答疑 </p>
<p><a class="btn btn-default" href="https://stackoverflow.com/search?q=spring-security" role="button">查看详情 »</a></p>
</div>
<div class="col-md-4">
<h2>Micro</h2>
<p>Spring Security 在微服务架构下的实践</p>
<p><a class="btn btn-default" href="https://github.com/ChinaSilence/micro" role="button">查看详情 »</a></p>
</div>
</div>
<hr>
<footer>
<p>© 2019 Power By <a href="https://anoyi.com">Anoyi</a> .</p>
</footer>
</div> <!-- /container -->
</body>
</html>
login.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录 | Spring Security Demos</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
</head>
<body style="background-color: #f1f1f1; padding-bottom: 0">
<div th:insert="~{header :: nav}"></div>
<div class="container" style="margin-top: 60px">
<div class="row" style="margin-top: 100px">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-console"></span>Login</h3>
</div>
<div class="panel-body">
<form th:action="@{/loginUser}" method="post">
<div class="form-group" style="margin-top: 30px">
<div class="input-group col-md-6 col-md-offset-3">
<div class="input-group-addon"><span class="glyphicon glyphicon-user"></span></div>
<input type="text" class="form-control" name="username" id="username" placeholder="账号">
</div>
</div>
<div class="form-group ">
<div class="input-group col-md-6 col-md-offset-3">
<div class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control" name="password" id="password"
placeholder="密码">
</div>
</div>
<br>
<div th:if="${param.error}">
<p style="text-align: center" class="text-danger">登录失败,账号或密码错误!</p>
</div>
<div th:if="${result}">
<p style="text-align: center" class="text-success" th:text="${result}"></p>
</div>
<div class="form-group">
<div class="input-group col-md-6 col-md-offset-3 col-xs-12 ">
<button type="submit" class="btn btn-primary btn-block">登录</button>
</div>
</div>
<div class="form-group">
<div class="input-group col-md-6 col-md-offset-3" style="text-align: center">
<a href="/register">创建账号</a> | 忘记密码?
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
效果图