首先,我是一个小白,写的东西有些读者可能看不懂,但是我写博客的目的一个是为了巩固自己学的一些知识,二是当做笔记,三是希望对有需要的人有些帮助。
我这我这里使用了redis+log4j2
不会redis可以去这个网址学习redis,此教程通俗易懂:https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0
- 在SpringBoot中pom.xml中引入相关的依赖包
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--安全测试-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--redis缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
- 在application.yml中配置Redis,并自定义RedisTemplate。我使用的是链接(https://www.jianshu.com/p/2909ee88cabb)中的RedisTemplate和RedisUtil工具类;SpringBoot整合Redis可以参考链接:https://www.jianshu.com/p/164ab8815b88
- 写一个业务层,业务层调用dao层的查询方法,根据用户在前端输入的账号,先去redis缓存中去查,如果缓存中没有,则去数据库查询该用户的账号信息。
- dao层接口
@Mapper
public interface UserMapper {
/**
* 根据用户账号查询用户账号相关的说有信息
* @param userAccount 用户输入的账号
* @return 返回的用户信息
*/
UserAccount queryUserByAccount(String userAccount);
}
- dao接口对应的Mapper.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">
<mapper namespace="com.littlenorth.dao.UserMapper">
<!--根据用户账号查询用户账号相关的说有信息-->
<select id="queryUserByAccount" resultType="com.littlenorth.dto.UserAccount">
select Account,Pwd from useraccount where Account=#{userAccount}
</select>
</mapper>
- service接口
public interface UserService {
/**
* 根据用户账号查询用户账号相关的说有信息
* @param userAccount
* @return
*/
UserAccount queryUserByAccount(String userAccount);
}
- service接口的实现类
@Slf4j //使用了log4j2日志
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; //调用dao层
@Autowired
private RedisUtil redisUtil; //调用redis工具类
/**
* 根据用户账号查询用户账号相关的说有信息
* @param userAccount 用户账号
* @return 返回的用户账号和密码
*/
@Override
public UserAccount queryUserByAccount(String userAccount) {
//判断缓存中是否存在键为user.getAccount()的用户
UserAccount cacheUserAccount = (UserAccount) redisUtil.get(userAccount);
if (cacheUserAccount !=null){
log.info("缓存中有数据!");
//如果缓存中有,则返回
return cacheUserAccount;
}else{
//如果缓存中没有,则从数据库中进行查询
UserAccount dataUserAccount = userMapper.queryUserByAccount(userAccount);
//查询之后将数据缓存 ,缓存一天之后过期
redisUtil.set(dataUserAccount.getAccount(), dataUserAccount,USER_CACHE_TIME);
log.info("缓存中没有数据!进数据库查询!");
return dataUserAccount;
}
}
}
- 写一个UserDetailsService接口的实现类,重写loadUserByUsername方法
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 java.util.ArrayList;
import java.util.List;
@Service //将类加入到spring容器中
public class UserDetailsServiceImpl implements UserDetailsService{
@Autowired
private UserService userService; //调用业务层
/**
* 重新父类方法
* @param userAccount 用户账号
* @return spring security自动封装之后的用户
* @throws UsernameNotFoundException 抛出异常
*/
@Override
public UserDetails loadUserByUsername(String userAccount) throws UsernameNotFoundException {
//获取用户账号信息
UserAccount user = userService.queryUserByAccount(userAccount);
//存储角色集合
List<GrantedAuthority> authorities = new ArrayList<>();
//添加like角色,角色必须以`ROLE_`开头,数据库中没有,则在这里加。
authorities.add(new SimpleGrantedAuthority("ROLE_like")); //like不是从数据库中查询出的角色
if (user!=null){
return new User(user.getAccount(),user.getPwd(),authorities);
}else{
throw new UsernameNotFoundException("当前用户名不存在!");
}
}
}
- 自定义登录成功处理类
import com.alibaba.fastjson.JSONObject;
import com.littlenorth.common.AjaxResult;
import com.littlenorth.common.Constants;
import com.littlenorth.constant.HttpStatus;
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;
/**
* 用户登录成功处理类
*/
@Component(value = "userAuthenticationSuccessHandler")
public class UserAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//自定义返回消息
AjaxResult result = new AjaxResult(HttpStatus.SUCCESS,Constants.LOGIN_SUCCESS);
//设置响应头
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(result));
}
}
- 自定义登录失败处理类
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录失败
*/
@Component
public class UserAuthenticationFailureHandler implements AuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
AjaxResult result = null;
if(e instanceof UsernameNotFoundException){
result=AjaxResult.error(e.getMessage(),Constants.DATA);
}else if(e instanceof BadCredentialsException){
result=AjaxResult.error(Constants.LOGIN_PWD_ERROR,Constants.DATA);
}else {
result=AjaxResult.error(e.getMessage());
}
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(result));
}
}
- 自定义未登录处理类
import com.alibaba.fastjson.JSONObject;
import com.littlenorth.common.AjaxResult;
import com.littlenorth.common.Constants;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义未登录的处理逻辑
*/
@Component
public class UserAuthenticationEnryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(new AjaxResult(Constants.REQUIRE_LOGIN)));
}
}
- 自定义无权限处理类
import com.alibaba.fastjson.JSONObject;
import com.littlenorth.common.AjaxResult;
import com.littlenorth.common.Constants;
import com.littlenorth.constant.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义无权限访问时的逻辑处理
*/
@Component
public class UserAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(new AjaxResult(HttpStatus.FORBIDDEN,Constants.NO_POWER)));
}
}
- 自定义验证逻辑处理类
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.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 自定义认证逻辑处理
*/
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取用户名
String account= authentication.getName();
// //获取密码
String password= (String) authentication.getCredentials();
UserDetails userDetails= userDetailsService.loadUserByUsername(account);
boolean checkPassword= passwordEncoder.matches(password,userDetails.getPassword());
if(!checkPassword){
throw new BadCredentialsException("密码不正确,请重新登录!");
}
return new UsernamePasswordAuthenticationToken(account,password,userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
- 创建SecurityConfig继承WebSecurityConfigurerAdapter,并重写configure(AuthenticationManagerBuilder auth)方法
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//注入自定义登录成功处理类
@Autowired
private UserAuthenticationSuccessHandler userAuthenticationSuccessHandler;
//注入自定义失败处理类
@Autowired
private UserAuthenticationFailureHandler authenticationFailureHandler;
//注入自定义未登录处理类
@Autowired
private UserAuthenticationEnryPoint userAuthenticationEnryPoint;
//无权访问
@Autowired
private UserAccessDeniedHandler accessDeniedHandler;
//未登录
@Autowired
private UserAuthenticationEnryPoint authenticationEnryPoint;
//自定义认证逻辑处理
@Autowired
private SelfAuthenticationProvider selfAuthenticationProvider;
/**
* 加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
/**
* 用户认证
* @param auth 用户认证
* @throws Exception 异常
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(selfAuthenticationProvider);
}
/**
* 用户访问控制
* @param http 请求
* @throws Exception 抛出异常
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// /user的所有请求都要有like权限,否则去登录页面
http.authorizeRequests()
.antMatchers("/dologin").permitAll()
.antMatchers("/user/**").authenticated() //"/login"不进行权限验证
//.anyRequest().authenticated() //其他的需要登陆后才能访问
.and()
.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/dologin") //登录的请求
.successHandler(userAuthenticationSuccessHandler) //登录成功之后的处理
.failureHandler(authenticationFailureHandler) //登录失败的处理
.and()
.csrf().disable()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler) //权限不足的时候的逻辑处理
.authenticationEntryPoint(authenticationEnryPoint); //未登录是的逻辑处理
}
}
此外文章中用到的常量和工具类
/**
* 通用常量信息
*/
public class Constants {
/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "登录成功!";
/**
* 登录失败
*/
public static final String LOGIN_PWD_ERROR = "密码错误!";
/**
* 返回数据
*/
public static final String DATA=null;
/**
* 未登录
*/
public static final String REQUIRE_LOGIN="请先登录!";
/**
* 无权限
*/
public static final String NO_POWER="没有权限!";
}
```java
import java.util.HashMap;
/**
* 操作消息提醒类
*/
public class AjaxResult extends HashMap<String, Object> {
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param msg 返回内容
*/
public AjaxResult(String msg){
super.put(MSG_TAG,msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (StringUtils.isNotNull(data))
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}
}
```java
/**
* 返回的状态码
*/
public class HttpStatus {
/**
* 操作成功
*/
public static final int SUCCESS = 200;
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
public static final int NO_CONTENT = 204;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
public static final int BAD_METHOD = 405;
public static final int CONFLICT = 409;
public static final int UNSUPPORTED_TYPE = 415;
public static final int ERROR = 500;
}
注意:我数据库中用户的密码存的是密文,即BCryptPasswordEncoder加密之后的密码。不懂的可以参考此链接的博客:https://blog.csdn.net/bookssea/article/details/109262109
看不懂可以参考更详细的Spring Security的文章:https://blog.csdn.net/weixin_42375707/article/details/110678638