使用的自定义登陆路径和退出路径,登陆验证这些都是自己写的,没有用自带了,自带了不好用。
看了很多教程,整理一个相对完整的教程出来。
纯干货,其他没必要的都没上。比如啥数据库啊,缓存啊啥的,都没有,毕竟写上这些代码太多了,文章长了反正我没兴趣看下去了。
纯后端的,全是接口,没有涉及页面,百度很多都是使用自带登陆啊这些的。难搞。
我属于是面向百度编程,高深一点的知识不太懂,有什么问题请大佬指出。感谢
话不多说,上代码:
最开始还是pom.xml。springboot 版本 :2.7.11-SNAPSHOT
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
接下来是SecurityConfig.java
import com.example.security.security.filter.JwtAuthenticationTokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Resource
private MyAccessDeniedHandle myAccessDeniedHandle;
@Resource
MyLogoutSuccessHandler myLogoutSuccessHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// hasRole 含有某角色(String role)非ROLE_开头
// hasAnyRole 含任意角色(String… roles)
// hasAuthority 含有某权限(String authority) ,权限标识
// hasAnyAuthority
// permitAll 允许所有访问
// denyAll 不允许所有访问
// isAnonymous 可匿名不登录访问
// isAuthenticated 身份认证后访问
// isRememberMe 记住我用户访问
// isFullyAuthenticated 非匿名且非记住我允许访问
http
.csrf().disable() //关闭csrf
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login","/test","/logout").permitAll() //允许全部访问
// .antMatchers("/user/**").hasAnyRole("admin","common") //只允许权限访问
// .anonymous() //允许匿名访问
// 除上面外的所有请求全部需要鉴权认证
.anyRequest() //任何其它请求
// .permitAll() //其他都放行了
.authenticated(); // 都需要身份认证
// http.cors(); //开启跨域
// 添加Logout filter
http.logout().logoutUrl("/user/logout").logoutSuccessHandler(myLogoutSuccessHandler);
http.exceptionHandling() //自定义认证异常处理类和授权异常处理类 接下面两个
.accessDeniedHandler(myAccessDeniedHandle) // 自定义无权限访问
.authenticationEntryPoint(myAuthenticationEntryPoint); // 自定义未登录返回
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JwtAuthenticationTokenFilter.java。jwt的过滤器,主要作用就是在请求路径时,获取jwtToken校验用户是否登陆了。(有点缺陷,配置了不需要鉴权认证的路径也会走这里。比如登陆请求。但是放通就行了,不影响使用。)
import com.alibaba.fastjson.JSONObject;
import com.example.security.security.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedHashMap;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 从客户端请求中获取 JWT
String tokenHeader = request.getHeader("token");
if (tokenHeader != null) {
try {
String str= JwtUtils.parseTokenToStr(tokenHeader);
// 通过token解析出来的值获取用户是否判断,比如读取redis里存的登陆用户。或者缓存里存的登陆用户
// 我这里固定判断loadUserByUsername方法里生成的uid
if (str.equals("admin")){
// 运行下面两行就代表用户已经登陆 这里我存的token存的值 其他地方可以根据Authentication获取到这个值
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(str,null,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}catch (Exception ex){
// 解析token报错,防止乱传token
logger.warn("解析token报错!!");
}
}
// 放通、进入security自带的过滤器
chain.doFilter(request, response);
}
//返回接口信息
public void errMsg(HttpServletResponse response,String msg) throws IOException {
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", HttpServletResponse.SC_BAD_GATEWAY);
jsonObject.put("message",msg);
jsonObject.put("data","");
response.getWriter().println(jsonObject);
response.getWriter().flush();
}
}
MyAccessDeniedHandle.java。
import com.alibaba.fastjson.JSONObject;
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;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 当访问接口没有权限时,自定义的返回结果
* 有没有权限是security自带的逻辑。
* @author Json
* @date 2021/11/10 11:04
*/
@Component
public class MyAccessDeniedHandle implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", "-1");
jsonObject.put("message","您没有权限访问此接口!!");
jsonObject.put("data","");
response.getWriter().println(jsonObject);
response.getWriter().flush();
}
}
MyAuthenticationEntryPoint.java
import com.alibaba.fastjson.JSONObject;
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;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 当未登录或者token失效访问接口时,自定义的返回结果
* @author Json
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", "-1");
jsonObject.put("message","请登录!");
jsonObject.put("data","");
response.getWriter().println(jsonObject);
response.getWriter().flush();
}
}
MyLogoutSuccessHandler.java
import com.alibaba.fastjson.JSONObject;
import com.example.security.security.utils.JwtUtils;
import org.springframework.context.annotation.Configuration;
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.util.LinkedHashMap;
/**
* 自定义退出处理类 返回成功
* 当访问http.logout().logoutUrl()配置的 退出登陆时返回
* @author ruoyi
*/
@Configuration
public class MyLogoutSuccessHandler implements LogoutSuccessHandler
{
/**
* 自定义的返回结果
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
String tokenHeader = request.getHeader("token");
if (tokenHeader != null) {
try {
String str= JwtUtils.parseTokenToStr(tokenHeader);
// 根据这个删除缓存的用户登陆信息
System.out.println(str+":退出成功");
errMsg(response,"退出成功");
}catch (Exception ex){
// 解析token报错,防止乱传token
System.out.println("解析token报错!!");
}
}
errMsg(response,"退出失败,用户未登录!!");
}
//返回借口信息
public void errMsg(HttpServletResponse response,String msg) throws IOException {
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", HttpServletResponse.SC_BAD_GATEWAY);
jsonObject.put("message",msg);
jsonObject.put("data","");
response.getWriter().println(jsonObject);
response.getWriter().flush();
}
}
UserDetailsServiceImpl.java,这个是security处理登陆逻辑的地方,哪里调用会访问这里看LoginController.java里面写的登陆请求。
import lombok.SneakyThrows;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// @SneakyThrows
// @Override
// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//
// 这里假设是通过username从数据库查询出来的数据
// UserData userData = new UserData(13,username,"123456");
//
// authorities用于权限控制
// List<GrantedAuthority> authorities = new ArrayList<>();
// 添加权限
// authorities.add(new SimpleGrantedAuthority("ADMIN"));
// return new LoginUser(userData);
// }
// 这个是没有封装UserDetails的使用方法
@SneakyThrows
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 这里假设是通过username从数据库查询出来的数据
Map<String,String> user = new HashMap<>();
user.put("username",username);
user.put("password","123456");
// authorities用于权限控制
List<GrantedAuthority> authorities = new ArrayList<>();
// 添加权限
// authorities.add(new SimpleGrantedAuthority("ADMIN"));
UserDetails userDetails = User.builder().username(user.get("username"))
.password(new BCryptPasswordEncoder().encode(user.get("password")))
.authorities(authorities).build();
return userDetails;
}
}
LoginController.java,定义接口的地方。
import com.alibaba.fastjson.JSONObject;
import com.example.security.security.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.LinkedHashMap;
/**
* @author Json
* @date 2021/10/29 14:46
*/
@Slf4j
@RequestMapping
@RestController
public class LoginController {
@Resource
private AuthenticationManager authenticationManager;
@GetMapping("/getUser")
public String getUser(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean authenticated = authentication.isAuthenticated();
log.info("authenticated:" + authenticated);
log.info("author:"+authentication.getAuthorities());
log.info("getCredentials:"+authentication.getCredentials());
log.info("getCredentials:"+authentication.getDetails());
// 这里可以获取到在jwt过滤器验证是否登陆后存下来的信息,具体看JwtAuthenticationTokenFilter的UsernamePasswordAuthenticationToken,存的str。
log.info("authenticated:" + authentication.getPrincipal());
return authentication.getPrincipal().toString();
}
@GetMapping("/user/get")
public String getResource(){
return "user/get";
}
@GetMapping("/test")
public String test(){
return "test1";
}
@GetMapping("/test2")
public String test2(){
return "userService.getTest()";
}
@PostMapping("/login")
public Object login(String username,String password){
System.out.println("login");
// 1.将用户登录的用户名、密码 封装成一个authentication对象
UsernamePasswordAuthenticationToken authenticationToken = new
UsernamePasswordAuthenticationToken(username, password);
// 2.authenticationManager来进行认证,
// 运行这个是调用继承 UserDetailsService类下面的loadUserByUsername方法
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 2.如果认证通过 利用userid生成应该jwt
// UserDetails o = (UserDetails) authentication.getPrincipal();
UserDetails o = (UserDetails) authentication.getPrincipal();
String jwtToken = JwtUtils.createStrToToken(o.getUsername());
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", HttpServletResponse.SC_BAD_GATEWAY);
jsonObject.put("message","登录成功");
jsonObject.put("data",jwtToken);
return jsonObject;
}
}
JwtUtils.java。生成jwtToken和解析jwtToken的工具类,很简单,可以自己扩展。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 这里为了简单 只做测试 可自封装工具类
*
* @author Json
* @date 2021/11/3 9:42
*/
public final class JwtUtils {
private static final String ACCESS_TOKEN_SECRET = "SGVZZSGQWQSDA";
/**
* 字符串创建token
* @param subject 1
* @return 1
*/
public static String createStrToToken(String subject)
{
return Jwts.builder()
.setSubject(subject)
.signWith(SignatureAlgorithm.HS256, ACCESS_TOKEN_SECRET)
.compact();
}
/**
* 获取String
* @param token 1
* @return 1
*/
public static String parseTokenToStr(String token)
{
Claims body = null;
try {
body = Jwts.parser().setSigningKey(ACCESS_TOKEN_SECRET).parseClaimsJws(token).getBody();
} catch (io.jsonwebtoken.ExpiredJwtException e) {
return e.getMessage();
}
return body.getSubject();
}
public static void main(String[] args) {
String str = createStrToToken("100031");
System.out.println(str);
String p = parseTokenToStr(str);
System.out.println(p);
}
}
最后就是关于security自带的验证了,比如说登陆失败啊这些错误提示,都是直接抛出异常的,所以这里就需要一个统一处理异常的地方。
GlobalExceptionHandler.java。全局异常处理,需要抛出异常这里才会获取到,如果try-cath处理了就不会进这里。
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.LinkedHashMap;
/**
* 全局异常处理
*/
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BadCredentialsException.class)
public Object exceptionHandler(BadCredentialsException ex){
log.warn(ex.toString());
return errMsg(ex.getMessage());
}
/**
* 指定拦截那一中类型
* @param ex 类型
* @return m
*/
@ExceptionHandler(Exception.class)
public Object exceptionHandler(Exception ex){
log.warn(ex.toString());
return errMsg(ex.getMessage());
}
//返回借口信息
public JSONObject errMsg(String msg){
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("code", "-1");
jsonObject.put("message",msg);
jsonObject.put("data","");
return jsonObject;
}
}
最后补充一个UserDetails的封装,毕竟UserDetails携带的参数太少,自己封装一个在UserDetailsServiceImpl 里代替UserDetails使用。
UserData .java
package com.example.security.security.entity;
import lombok.Data;
@Data
public class UserData {
private int uid;
private String uName;
private String uPwd;
public UserData(){};
public UserData(int uid, String uName, String uPwd) {
this.uid = uid;
this.uName = uName;
this.uPwd = uPwd;
}
}
LoginUser.java
package com.example.security.security.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Collection;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private UserData userData;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return new BCryptPasswordEncoder().encode(userData.getUPwd());
}
@Override
public String getUsername() {
return userData.getUName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
到这里一个完整的security教程就结束了,最后附上项目目录。
我属于是面向百度编程,高深一点的知识不太懂,有什么问题请大佬指出。感谢
如果有什么问题,欢迎大佬指正,最后如果对你有一点点帮助,麻烦支持一下。
全国寄快递5元起,电影票8.8折。更多优惠微信关注公众号:【折价寄件】
感谢观看!!!!