1 spring boot集成spring security
1.1 初始化工程
数据库
创建一个 t_member 表存储用户信息
DROP TABLE IF EXISTS `t_member`;
CREATE TABLE `t_member` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO `t_member` (`id`, `username`, `password`) VALUES (1, 'admin', '123456');
INSERT INTO `t_member` (`id`, `username`, `password`) VALUES (2, 'yuwen', '123456');
INSERT INTO `t_member` (`id`, `username`, `password`) VALUES (3, 'yulei', '123456');
pom.xml
<dependencies>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis-spring-boot-starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://10.19.11.107:3306/demo-all?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
main:
allow-bean-definition-overriding: true
mybatis:
type-aliases-package: com.example.demooauth2authorization.dao
configuration:
map-underscore-to-camel-case: true
mapper-locations: classpath:mapper/*.xml
logging:
level:
root: info
com.example.demooauth2authorization.dao: debug
MemberController.java
@RestController
public class MemberController {
@Autowired
private MemberDao memberDao;
/**
* 查询所有用户
*
* @return List<User>
*/
@GetMapping("getAll")
public List<Member> getAll() {
return memberDao.getAll();
}
}
MemberDao.java
public interface MemberDao {
/**
* 查询所有用户
*
* @return List<User>
*/
List<Member> getAll();
}
Member.java
@Data
public class Member {
private Integer id;
private String username;
private String password;
}
MemberDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!-- 映射文件,映射到对应的SQL接口 -->
<mapper namespace="com.example.demooauth2authorization.dao.MemberDao">
<!--查询所有用户-->
<select id="getAll" resultType="com.example.demooauth2authorization.entity.Member">
select *
from t_Member
</select>
<!--根据用户名获取用户-->
<select id="getUser" resultType="com.example.demooauth2authorization.entity.Member">
select *
from t_Member
where username = #{username}
</select>
</mapper>
hah,没有service层,因为这是一个demo,怎么简单怎么来。
启动工程,访问:http://127.0.0.1:8080/getAll
可以看到数据正常展示。
1.2 集成spring security
加入依赖:
<!--spring-boot-starter-security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
新建类实现UserDetailService接口重写loadUserByUsername方法。
CustomUserDetailsServiceImpl.java
package com.example.demooauth2authorization.security;
import com.example.demooauth2authorization.dao.MemberDao;
import com.example.demooauth2authorization.entity.Member;
import com.example.demooauth2authorization.util.PasswordEncoderUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author 相柳
* @date 2021/9/4
*/
@Component
@Slf4j
public class CustomUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private MemberDao memberDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("调用loadUserByUsername");
// 通过用户名从数据库获取用户信息
Member member = memberDao.getUser(username);
if (member == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 角色集合
List<GrantedAuthority> roles = new ArrayList<>();
return new User(
member.getUsername(),
// 因为数据库是明文,所以这里需加密密码
PasswordEncoderUtil.encode(member.getPassword()),
roles
);
}
}
PasswordEncoderUtil.java
package com.example.demooauth2authorization.util;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author 相柳
* @date 2021/9/6
*/
public class PasswordEncoderUtil {
public static Boolean matches(String raw, String encoded) {
return new BCryptPasswordEncoder().matches(raw, encoded);
}
public static String encode(String raw) {
return new BCryptPasswordEncoder().encode(raw);
}
}
创建Security的配置类WebSecurityConfig继承WebSecurityConfigurerAdapter,并重写configure(auth)方法。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsServiceImpl customUserDetailsService;
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoder() {
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
// 从数据库读取的用户进行身份认证
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
}
重启工程,再次访问:http://127.0.0.1:8080/getAll
出现如下登录界面,这是spring security默认的登录页。输入数据库中的用户信息,登录进入即可
2 spring security的配置
2.1 自定义登录页
使用freemarker作为模板,加入依赖:
<!--spring-boot-starter-freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
然后把登录界面和欢迎页面写好,
login.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>微服务统一认证</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body class="sign_body">
<div class="container form-margin-top">
<form class="form-signin" action="/member/login" method="post" style="width: 300px;margin: 0px auto;">
<h2 class="form-signin-heading" align="center">自定义统一认证系统</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus><br/>
<input type="password" name="password" class="form-control" placeholder="密码" required><br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button><br/>
<#if error??>
<span style="color: red; ">${error}</span>
</#if>
</form>
</div>
</body>
</html>
welcome.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>微服务统一认证</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body class="sign_body">
<div class="container form-margin-top">
尊敬的:<span style="color: red">
<#if username??>
${username}
</#if>
</span>,欢迎您
</div>
</body>
</html>
自定义 LoginValidProvider 继承自 AuthenticationProvider。实现认证逻辑
LoginValidProvider.java
package com.example.demooauth2authorization.security;
import com.example.demooauth2authorization.dao.MemberDao;
import com.example.demooauth2authorization.entity.Member;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* @author 相柳
* @date 2021/9/6
*/
@Component
@Slf4j
public class LoginValidProvider implements AuthenticationProvider {
@Autowired
private MemberDao memberDao;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.info("身份认证开始工作");
String username = authentication.getName();
String rawPassword = (String) authentication.getCredentials();
// 数据库中查询用户
Member member = memberDao.selectByUnameAndUPass(username, rawPassword);
if (member != null) {
return new UsernamePasswordAuthenticationToken(member, rawPassword, new ArrayList<>());
} else {
throw new BadCredentialsException("用户名或者密码错误");
}
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
然后自定义认证成功、认证失败和登出的处理函数(这是可选的)
LoginSuccessHandle.java
package com.example.demooauth2authorization.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 相柳
* @date 2021/9/6
*/
@Slf4j
@Component
public class LoginSuccessHandle implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("进入登录成功handle");
response.sendRedirect("/member/toWelcome");
}
}
LoginFailHandle.java
package com.example.demooauth2authorization.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
/**
* @author 相柳
* @date 2021/9/7
*/
@Slf4j
@Component
public class LoginFailHandle implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("进入登录失败handle");
String toLogin = "/member/toLogin?error=";
String url = toLogin + URLEncoder.encode(exception.getMessage(), "UTF-8");
response.sendRedirect(url);
}
}
LogoutHandle.java
package com.example.demooauth2authorization.security;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 相柳
* @date 2021/9/7
*/
@Slf4j
@Component
public class LogoutHandle implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登出handle");
// 获取请求参数中是否包含回调地址
String redirectUrl = request.getParameter("redirect_url");
if (StringUtils.isNotBlank(redirectUrl)) {
response.sendRedirect(redirectUrl);
}
else {
// 默认跳转referer 地址
String referer = request.getHeader(HttpHeaders.REFERER);
response.sendRedirect(referer);
}
}
}
接下来配置 WebSecurityConfig.java 。这是最重要的
package com.example.demooauth2authorization.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author 相柳
* @date 2021/9/4
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginValidProvider loginValidProvider;
@Autowired
private LoginSuccessHandle loginSuccessHandle;
@Autowired
private LoginFailHandle loginFailHandle;
@Autowired
private LogoutHandle logoutHandle;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoder() {
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
String toLogin = "/member/toLogin";
String checkPath = "/member/login";
http
// 自定义登录页
.formLogin()
// 登录表单提交的url,即和form的action一致
.loginProcessingUrl(checkPath)
// 访问登录页的url
.loginPage(toLogin)
// 设置登录成功和失败的处理器
.successHandler(loginSuccessHandle)
.failureHandler(loginFailHandle)
// 设置登出操作
.and().logout()
.logoutSuccessHandler(logoutHandle)
.deleteCookies("JSESSIONID").invalidateHttpSession(true)
// 登录请求不需要认证
.and().authorizeRequests().antMatchers(toLogin).permitAll()
// 其他的所有请求都需要认证
.anyRequest().authenticated()
// 关闭csrf防护
.and().csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置自定义认证
auth.authenticationProvider(loginValidProvider);
}
}
2.2 登录成功自动跳转到登录前的地址
改造之前的 LoginSuccesshandle.java
package com.example.demooauth2authorization.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author 相柳
* @date 2021/9/6
*/
@Slf4j
@Component
public class LoginSuccessHandle implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("进入登录成功handle");
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
response.sendRedirect("/member/toWelcome");
return;
}
// 返回之前请求的地址
clearAuthenticationAttributes(request);
String targetUrl = savedRequest.getRedirectUrl();
response.sendRedirect(targetUrl);
}
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
}
这段代码是仿照 spring security 内置的 SavedRequestAwareAuthenticationSuccessHandler 所写。SavedRequestAwareAuthenticationSuccessHandler 也有在登录后返回原来请求地址的作用。所以登录成功的处理逻辑也可以去继承它来实现跳转到之前的页面
--------------------------------------------------------
这篇博客是我看博客,看文档,看源码,然后结合一个开源项目和自身4年开发经验写出来的,仅供参考,留以备忘。