Spring boot +security+jwt整合新手教程 完整版 纯后端

使用的自定义登陆路径和退出路径,登陆验证这些都是自己写的,没有用自带了,自带了不好用。

看了很多教程,整理一个相对完整的教程出来。

纯干货,其他没必要的都没上。比如啥数据库啊,缓存啊啥的,都没有,毕竟写上这些代码太多了,文章长了反正我没兴趣看下去了。

纯后端的,全是接口,没有涉及页面,百度很多都是使用自带登陆啊这些的。难搞。

我属于是面向百度编程,高深一点的知识不太懂,有什么问题请大佬指出。感谢

话不多说,上代码:

最开始还是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折。更多优惠微信关注公众号:【折价寄件】

感谢观看!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值