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

感谢观看!!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于构建微服务的开源框架,它能够快速搭建项目并且提供了许多便捷的功能和特性。Spring Security 是一个用于处理认证和授权的框架,可以保护我们的应用程序免受恶意攻击。JWT(JSON Web Token)是一种用于身份验证的开放标准,可以被用于安全地传输信息。Spring MVC 是一个用于构建 Web 应用程序的框架,它能够处理 HTTP 请求和响应。MyBatis 是一个用于操作数据库的框架,可以简化数据库操作和提高效率。Redis 是一种高性能的键值存储系统,可以用于缓存与数据存储。 基于这些技术,可以搭建一个商城项目。Spring Boot 可以用于构建商城项目的后端服务,Spring Security 可以确保用户信息的安全性,JWT 可以用于用户的身份验证,Spring MVC 可以处理前端请求,MyBatis 可以操作数据库,Redis 可以用于缓存用户信息和商品信息。 商城项目的后端可以使用 Spring BootSpring Security 来搭建,通过 JWT 来处理用户的身份验证和授权。数据库操作可以使用 MyBatis 来简化与提高效率,同时可以利用 Redis 来缓存一些常用的数据和信息,提升系统的性能。前端请求则可以通过 Spring MVC 来处理,实现商城项目的整体功能。 综上所述,借助于 Spring BootSpring SecurityJWTSpring MVC、MyBatis 和 Redis 这些技术,可以构建出一个高性能、安全可靠的商城项目,为用户提供良好的购物体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值