Spring Security的搭建
一. 认证
1.1 引入依赖
<!--springboot整合security坐标-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.2 创建一个service类 实现UserDetailsService接口
重写loadUserByUsername(String account)方法
@Service
public class SecurityLoginService implements UserDetailsService {
@Autowired(required = false)
private PasswordEncoder passwordEncoder;
@Autowired(required = false)
private UserDao userDao;
/**
* 根据页面传过来的account 去数据库查询该账号信息,放到security提供的user对象中
* @param account 账号
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
Users users = userDao.queryUserInfoAndAuths(account);
if (users!=null){
String auths = String.join(",", users.getAuth());
return new User(users.getAccount(),passwordEncoder.encode(users.getPassword()),
AuthorityUtils.commaSeparatedStringToAuthorityList(auths));
}else{
throw new UsernameNotFoundException("用户名和密码错误!");
}
}
}
1.3 创建一个config类 继承WebSecurityConfigurerAdapter
重写configure(AuthenticationManagerBuilder auth)方法
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityLoginService securityLoginService;
@Bean
public PasswordEncoder getPassword(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(securityLoginService).passwordEncoder(getPassword());
}
}
二. 认证(自定义登录页面,非前后端分离项目用)
2.1 在SecurityConfig中重写configure(HttpSecurity http)方法
@Override
protected void configure(HttpSecurity http) throws Exception {
//告诉security使用自定义登录页面
http.formLogin()
.loginPage("/login.html") //指定用户登录页面地址
.loginProcessingUrl("/doLogin") //表单提交地址,对应表单Action属性
.permitAll();//除了上面配置的地址,其他请求都会被拦截
http.authorizeRequests()
.anyRequest().authenticated();//所有请求都被拦截
http.csrf().disable();//关闭跨站脚本攻击
}
三. 配置处理类
3.1 登录成功返回处理类
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
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;
import java.io.PrintWriter;
/**
* 前后端分离的项目情况下,登录成功后返回的不再是一个页面,而是一个json
* 处理用户登录成功后返回给前端的数据:比如用户名等信息
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//设置字符集
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(ResponseResult.SECCUSS);
pw.print(json);
pw.flush();
pw.close();
}
}
3.2 登录失败返回处理类
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 登录失败后返回给前端的提示信息
*/
public class LoginFailHandler implements AuthenticationFailureHandler
{
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(ResponseResult.FAIL);
pw.print(json);
pw.flush();
pw.close();
}
}
3.3 拦截没有权限访问该资源的操作
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 虽然知道用户名密码
* 拦截没有权限访问该资源的操作
*/
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(ResponseResult.NOAUTH);
pw.print(json);
pw.flush();
pw.close();
}
}
3.4用户未登录直接访问系统资源
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 前后端分离
* 用户未登录直接访问系统资源
* 会被该类拦截
*/
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(ResponseResult.NOLOGIN);
pw.print(json);
pw.flush();
pw.close();
}
}
3.5 用户退出处理
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 用户退出
*/
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(ResponseResult.LOGOUT);
pw.print(json);
pw.flush();
pw.close();
}
}
四. 整合JWT
4.1 导入JWT依赖和Redis依赖
<!--JWT依赖-->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.11.1</version>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 使用 lettuce 时要加这个包;使用 jedis 时则不需要。-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
4.2 导入JWT工具类
package com.woniu.util;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import java.util.HashMap;
import java.util.Map;
/**
* 生成JWT工具类
* 1: 创建jwt
* 2: 校验jwt是否合法
* 3: 返回载荷部分
*/
public class JWTUtil {
private static final String KEY="lfasdkjafjlfjdslafjaslfjsaflsjflsjlasjfljlasfjlsfjlsl";
public static String createJWT(String username) throws Exception {
/**
* 第一部分:头部
*/
//创建JWT的头部
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT).build();
/**
* 第二部分: 搭载用户信息的地方
*/
Map map=new HashMap();
map.put("username",username);
Payload payload=new Payload(map);
/**
* 第三个部分:
*/
//1.先把头部和载荷加到一起
JWSObject jwsObject=new JWSObject(jwsHeader,payload);
//2.创建一个密钥放到JWSSigner中去
JWSSigner jwsSigner=new MACSigner(KEY);
//3.根据密钥把jwsObject加密
jwsObject.sign(jwsSigner);
//4.把jwt序列化成一个字符串
String serialize = jwsObject.serialize();
return serialize;
}
/**
* 拿到参数jwt,根据密钥解码
*/
public static boolean decode(String jwt) throws Exception {
JWSVerifier jwsVerifier = new MACVerifier(KEY);
//把jwt字符串转成一个jwt对象
JWSObject parse = JWSObject.parse(jwt);
boolean verify = parse.verify(jwsVerifier);
return verify;
}
/**
* 获取jwt载荷
*/
public static Map getPayload(String jwt) throws Exception {
JWSObject jwsObject = JWSObject.parse(jwt);
Map map = jwsObject.getPayload().toJSONObject();
return map;
}
}
4.3 登录成功处理 返回jwt
package com.woniu.handler;
import com.alibaba.fastjson.JSON;
import com.woniu.util.JWTUtil;
import com.woniu.util.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
* 前后端分离的项目情况下,登录成功后返回的不再是一个页面,而是一个json
* 处理用户登录成功后返回给前端的数据:比如用户名等信息
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
try {
//获取登录成功的用户信息
User user = (User) authentication.getPrincipal();
String username = user.getUsername();
String jwt = JWTUtil.createJWT(username);
//TimeUnit.SECONDS: 秒
//TimeUnit.MINUTES: 分钟
redisTemplate.opsForValue().set("jwt:"+user.getUsername(), jwt,1000, TimeUnit.SECONDS);
//设置字符集
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter pw = httpServletResponse.getWriter();
String json = JSON.toJSONString(new ResponseResult<>().ok(jwt));
pw.print(json);
pw.flush();
pw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:修改config类: successHandler(loginSuccessHandler),并把LoginSuccessHandler载入容器中
4.4 security 整合jwt用的过滤器
/**
* security 整合jwt用的过滤器
* 功能:
* 1: 判断除登录请求外是否携带了jwt, 是: 继续执行下面的逻辑 否: 放掉不处理
* 2: 判断携带的jwt是否合法, 是: 继续执行下面的逻辑 否: 放掉不处理
* 3: 拿redis的jwt和请求头的jwt做对比
* 1): redis的jwt已经过期 放掉不处理
* 2): 再把jwt的值对比一下 放掉不处理
*/
@Component
public class JWTFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String ,String> redisTemplate;
@Autowired
private SecurityLoginService securityService;
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//在请求头拿到jwt
String jwt = httpServletRequest.getHeader("jwt");
//1: 判断除登录请求外是否携带了jwt, 否: 放掉不处理
if (jwt == null){
//放给security 其他过滤器处理, 该方法不做处理
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//2: 判断携带的jwt是否合法, 否: 放掉不处理
if (!JWTUtil.decode(jwt)){
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//3: 拿redis的jwt和请求头的jwt做对比
//3.1: 获取jwt的用户信息
Map payload = JWTUtil.getPayload(jwt);
String username = (String) payload.get("username");
//3.2: 拿到redis的jwt
String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);
//1): redis的jwt已经过期 放掉不处理
if (redisJWT == null){
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//2): 再把jwt的值对比一下 放掉不处理
if (!jwt.equals(redisJWT)){
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//给redis的jwt续期
redisTemplate.opsForValue().set("jwt:" + username,jwt,2, TimeUnit.MINUTES);
//获取用户名,密码,权限
UserDetails userDetails = securityService.loadUserByUsername(username);
//获取用户信息 生成security容器凭证
UsernamePasswordAuthenticationToken upa=new UsernamePasswordAuthenticationToken(userDetails.getUsername(),userDetails.getPassword(),userDetails.getAuthorities());
//往security容器放入凭证
SecurityContextHolder.getContext().setAuthentication(upa);
//本方法功能执行完了, 交给下一个过滤器
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}