Spring——Security前后分离校验token_springsecurity前后端分离token验证-程序员宅基地

技术标签: spring  spring security  java  后端  

前言

之前采取项目中嵌套html页面,实现基本的登录校验权限校验登出操作记住我等功能试下。

但是,现在的开发基本都是前后分离样式,后端并不需要配置登录页的操作。

如何才能做到前后分离,同时也能支持登录token校验呢,本篇博客详细说明。

token配置

本次token生成采取jwt的方式。

为什么会考虑使用jwt,主要是因为平时开发中,采取数据库增加token字段,通过token查询数据库获取对应用户信息。

但是jwt本身就具有将信息转换为token,同时也能根据token转换信息的功能,能尽量缩减放问数据库的频次,提升数据查询处理效率。

引入JWT依赖文件

<dependency>
  <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

配置token管理器类

自定一个Token生成和从token中解析用户名的一个类,并交给Spring管理。

package security.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.HashMap;

@Component
public class TokenJwtManager {
    

    // 设置token时间
    private int tokenEcpiration = 24*60*60*1000; // 毫秒   24h

    // 编码密钥
    private String tokenSignKey = "123456";

    // 1、根据用户名生成token
    public String createToken(String userName){
    
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, tokenEcpiration);

        String userName1 = JWT.create()
                .withHeader(new HashMap<>())
                .withClaim("userName", userName)
                .withExpiresAt(calendar.getTime()) // 过期时间
                .sign(Algorithm.HMAC256(tokenSignKey));// 签名
        return userName1;
    }

    // 2、根据token得到用户名信息
    public String getUserName(String token){
    
        JWTVerifier build = JWT.require(Algorithm.HMAC256(tokenSignKey)).build();
        DecodedJWT verify = build.verify(token);
        Claim userName = verify.getClaim("userName");
        return userName.asString();
    }

    public static void main(String[] args) {
    
        String ss = new TokenJwtManager().createToken("1111111");
        System.out.println(ss);
        System.out.println(new TokenJwtManager().getUserName(ss));
    }
}

security 配置

配置未登录处理类

package security.config.handler;

import lombok.extern.slf4j.Slf4j;
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.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 未登录
 */
@Component
@Slf4j
public class MyUnAuthEntryPoint implements AuthenticationEntryPoint {
    
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    
        log.info("======= commence ===");
        // 返回请求端
        Map<String,Object> resultMap = new HashMap<>();
        // 保存数据
        resultMap.put("code","10000");
        resultMap.put("msg","当前账户未登录");
        resultMap.put("data",new HashMap<>());

        // 设置返回消息类型
        httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        // 返回给请求端
        PrintWriter writer  = httpServletResponse.getWriter();
        writer.write(resultMap.toString());
        writer.close();
    }
}

配置无权限处理类

package security.config.handler;

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.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 无权访问配置(前后分离)
 */
@Component  // 交给spring管理
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    
        Map<String,Object> resultMap = new HashMap<>();
        // 保存数据
        resultMap.put("code","403");
        resultMap.put("msg","无权访问");
        resultMap.put("data",null);

        // 设置返回消息类型
        httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        // 返回给请求端
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write(resultMap.toString());
        writer.flush();
        writer.close();
    }
}

配置登出操作处理类

package security.config.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;


/**
 * 登出
 */
@Component
@Slf4j
public class MyLogoutHandler implements LogoutHandler {
    

    @Autowired
    private TokenJwtManager tokenJwtManager;

//    public MyLogoutHandler(TokenJwtManager tokenJwtManager) {
    
//        this.tokenJwtManager = tokenJwtManager;
//    }

    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
    
        // 1、从header中获取token
        String token = httpServletRequest.getHeader("token");
        log.info("token信息为  {}",token);
        String userName = tokenJwtManager.getUserName(token);
        log.info("从token获取userName信息为  {}",token);

        // redis 移除登录信息等逻辑
        // xxxxx

        // 2、返回请求端
        Map<String,Object> resultMap = new HashMap<>();
        // 保存数据
        resultMap.put("code","200");
        resultMap.put("msg",userName+"登出成功");
        resultMap.put("data",new HashMap<>());

        // 设置返回消息类型
        httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        // 返回给请求端
        PrintWriter writer = null;
        try {
    
            writer = httpServletResponse.getWriter();
            writer.write(resultMap.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
    
           e.printStackTrace();
        }

    }
}

配置token认证过滤器

package security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;
import security.vo.SecurityUser;
import security.vo.User;

import javax.servlet.FilterChain;
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.ArrayList;
import java.util.HashMap;
import java.util.Map;

// 这里交给spring管理会报错
@Slf4j
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    

    private TokenJwtManager tokenJwtManager;
    private AuthenticationManager authenticationManager;

    public TokenLoginFilter(TokenJwtManager tokenJwtManager, AuthenticationManager authenticationManager) {
    
        this.tokenJwtManager = tokenJwtManager;
        this.authenticationManager = authenticationManager;
        this.setPostOnly(false); // 关闭登录只允许 post
        // 设置登陆路径,并且post请求
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/user/login","POST"));
    }

    // 1、获取登录页传递来的账户和密码信息
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
    
        log.info("==== attemptAuthentication  ======");

        String userName = request.getParameter("userName");
        String pwd = request.getParameter("passWord");
        log.info("userName:{},pwd:{}",userName,pwd);
		
		// 登录接口 /user/login 调用请求时触发
		// UsernamePasswordAuthenticationToken 封装登录时传递来的数据信息
		// 交给 AuthenticationManager  进行登录认证校验
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userName,
                pwd,new ArrayList<>()));
    }

    // 2、认证成功调用
    @Autowired
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
    
        log.info("==== successfulAuthentication  ======");
        // 认证成功之后,获取认证后的用户基本信息
        SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();

        // 根据用户名生成对应的token
        String token = tokenJwtManager.createToken(securityUser.getUsername());

        // token信息存于redis、数据库、缓存等

        // 返回成功
        Map<String,Object> resultMap = new HashMap<>();
        // 保存数据
        resultMap.put("code","200");
        resultMap.put("msg","登录成功");
        resultMap.put("data",token);

        // 设置返回消息类型
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=UTF-8");
        // 返回给请求端
        PrintWriter writer = response.getWriter();
        writer.write(resultMap.toString());
        writer.flush();
        writer.close();
    }

    // 3、认证失败调用的方法
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
    
        log.info("==== unsuccessfulAuthentication  ======");
        Map<String,Object> resultMap = new HashMap<>();
        // 保存数据
        resultMap.put("code","500");
        resultMap.put("msg","登录验证失败");
        resultMap.put("data",new HashMap<>());

        // 设置返回消息类型
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=UTF-8");
        // 返回给请求端
        PrintWriter writer = response.getWriter();
        writer.write(resultMap.toString());
        writer.flush();
        writer.close();
    }

}

配置token权限校验过滤器

package security.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;

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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * token 校验
 */
@Slf4j
//@Component // 交给 spring 会报错
public class TokenAuthFilter extends BasicAuthenticationFilter {
    

    private TokenJwtManager tokenJwtManager;

    public TokenAuthFilter(AuthenticationManager authenticationManager, TokenJwtManager tokenJwtManager) {
    
        super(authenticationManager);
        this.tokenJwtManager = tokenJwtManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
        log.info("====  doFilterInternal   ==========   token校验");

        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
        if(authRequest != null){
    
            // 有权限,则放入权限上下文中
            SecurityContextHolder.getContext().setAuthentication(authRequest);
        }
        // 执行下一个 filter 过滤器链
        chain.doFilter(request,response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    
        log.info("==== getAuthentication =====");
        //从header获取token
        String token = request.getHeader("token");
        log.info("token:{}",token);
        if(token != null) {
    
            //从token获取用户名
            String username = tokenJwtManager.getUserName(token);
            log.info("解析token获取userName为:{}",username);
            // 登录成功时,会将权限数据存入redis
            // 这里是验证获取权限信息
                // 1、从redis中获取对应该用户的权限信息
                // 2、或从数据库中再次查询
            // 本次模拟
            List<String> permissionValueList = Arrays.asList("admin","select");
            Collection<GrantedAuthority> authority = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
    
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
                authority.add(auth);
            }
            return new UsernamePasswordAuthenticationToken(username,token,authority);
        }
        return null;
    }
}

自定义加密类

package security.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import security.utils.Md5Utils;

/**
 * 密码加密、比对
 */
@Component // bean
@Slf4j
public class DefaultPwdEndoder implements PasswordEncoder {
    

    /**
     * 加密
     * @param charSequence
     * @return
     */
    @Override
    public String encode(CharSequence charSequence) {
    
        log.info("==== encode ====");
        log.info("charSequence 为 {}",charSequence);
        log.info("charSequence md5为 {}",Md5Utils.md5(charSequence.toString()));
        return Md5Utils.md5(charSequence.toString());
    }

    /**
     * 进行密码比对
     * @param charSequence 不加密
     * @param encodePwd  加密
     * @return
     */
    @Override
    public boolean matches(CharSequence charSequence, String encodePwd) {
    
        log.info("==== matches ====");
        log.info("charSequence:{}",charSequence);
        log.info("charSequenceMd5:{}",Md5Utils.md5(charSequence.toString()));
        log.info("encodePwd:{}",encodePwd);
        return encodePwd.equalsIgnoreCase(Md5Utils.md5(charSequence.toString()));
    }
}

配置UserDetailService

package security.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.Component;
import org.springframework.stereotype.Service;
import security.mapper.UserMapper;
import security.vo.SecurityUser;
import security.vo.User;

import java.util.Arrays;
import java.util.List;

/**
 * security 登录信息和权限获取类
 */
@Service("userDetailsService")
@Slf4j
public class UserDetailService implements UserDetailsService {
    
    // 注入Usermapper
    @Autowired
    private UserMapper userMapper;


    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    
        log.info("====== loadUserByUsername ======");
        // 通过username查询数据库获取用户信息
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username",userName);
        User user = userMapper.selectOne(userQueryWrapper);

        // 判断用户是否存在
        if(user == null){
    
            throw new UsernameNotFoundException("账户信息不存在!");
        }
        // 存在对应的用户信息,则将其封装,丢给security自己去解析
        log.info("user:{}",user);
		
		// 登录时,会走这个接口
        // 权限暂时不查数据库
        List<String> admin = Arrays.asList("ROLE_user,ROLE_admin,admin");

        // 将数据封装给 SecurityUser ,因为 SecurityUser 是 UserDetails 的子类
        SecurityUser securityUser = new SecurityUser();
        securityUser.setPermissionValueList(admin);
        securityUser.setUser(user);

        log.info("securityUser:{}",securityUser.toString());
        return securityUser;
    }
}

配置数据库User对象映射类

package security.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    
    private static final long serialVersionUID = -5461108964440966122L;

    private Integer id;
    private String username;
    private String password;
    private Integer enabled;
    private Integer locked;
}

配置UserDetailService使用的SecurityUser类

package security.vo;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * UserDetailService 使用该类,该类必须是 UserDetails 的子类
 */
@Data
public class SecurityUser implements UserDetails {
    

    // 登录用户的基本信息
    private User user;

    //当前权限
    private List<String> permissionValueList;

    public SecurityUser() {
    
    }

    public SecurityUser(User user) {
    
        if (user != null) {
    
            this.user = user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        permissionValueList.forEach(permission ->{
    
            if(!StringUtils.isEmpty(permission)){
    
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
                authorities.add(authority);
            }
        });
        return authorities;
    }

    @Override
    public String getPassword() {
    
        return user.getPassword();
    }

    @Override
    public String getUsername() {
    
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
    
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
        return true;
    }

    @Override
    public boolean isEnabled() {
    
        return true;
    }
}

配置mybatis-plus

首先,需要配置application.properties数据库连接源。

spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://106.55.137.66:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

server.port=80

其次,需要配置Mapper类,查询数据库获取基本数据信息。

package security.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
import security.vo.User;

@Repository
public interface UserMapper extends BaseMapper<User> {
    
}

配置security配置类

package security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import security.config.handler.*;
import security.filter.TokenAuthFilter;
import security.filter.TokenLoginFilter;
import security.service.UserDetailService;

/**
 * security 配置类
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 方法增加权限
public class MyTokenSecurityConfig extends WebSecurityConfigurerAdapter {
    

    // 将 UserDetailService 注入,使其去查询数据库
    @Autowired
    private UserDetailService userDetailsService;

    // token 生成器
    @Autowired
    private TokenJwtManager tokenManager;

    // 自定义密码加密解密
    @Autowired
    private DefaultPwdEndoder defaultPwdEndoder;

    // 未登录handler
    @Autowired
    private MyUnAuthEntryPoint myUnAuthEntryPoint;

    // 无权限
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    //  登出handler处理
    @Autowired
    private MyLogoutHandler myLogoutHandler;

    // 登录失败
    @Autowired
    private LoginFailedHandler loginFailedHandler;

    // 登录成功
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    /**
     * 登录时,从数据库获取基本信息和权限信息
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
        // 设置 userDetailsService 和 密码解析
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPwdEndoder);
    }

    /**
     * 配置访问过滤
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
        http.exceptionHandling()
                .authenticationEntryPoint(myUnAuthEntryPoint) // 未登录 handler
                .accessDeniedHandler(myAccessDeniedHandler) // 无权限

                .and().csrf().disable() // 关闭 csrf 跨域请求

                .formLogin()
                .loginProcessingUrl("/user/login")  // 设定登录请求接口
                .usernameParameter("userName")
                .passwordParameter("passWord")
                //.successHandler(loginSuccessHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用
                //.failureHandler(loginFailedHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用
                .permitAll()

                .and()
                .authorizeRequests() // 请求设置
                .antMatchers("/test").permitAll() // 配置不需要认证的接口
                .anyRequest().authenticated() // 任何请求都需要认证

                .and()
                .logout() // logout设定
                .logoutUrl("/logouts")  //退出请求  /logouts 未定义,交给自定义handler实现功能
                .addLogoutHandler(myLogoutHandler) // 登出 myLogoutHandler 处理

                .and()
                .addFilter(new TokenLoginFilter(tokenManager,authenticationManager())) // 认证交给 自定义 TokenLoginFilter 实现
                .addFilter(new TokenAuthFilter(authenticationManager(),tokenManager))
                .httpBasic();

    }

    /**
     * 配置不需要验证的访问路径
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    
        //web.ignoring().antMatchers("/test","/user/login");
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }
}

配置几个测试接口

package security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    


    @RequestMapping("/test")
    public String test(){
    
        return "不需要认证就能访问";
    }
}

package security.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    

    @RequestMapping("/user/test1")
    @PreAuthorize("hasAnyAuthority('admin','user')")
    public String test1(){
    
        return "需要认证的 /user/test1";
    }


    @RequestMapping("/user/test2")
    @PreAuthorize("hasAnyAuthority('test')")
    public String test2(){
    
        return "需要认证的 /user/test2";
    }
}

Md5加密工具类

package security.utils;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * 加密工具类
 */
public class Md5Utils {
    

    public static String md5(String str) {
    
        try {
    
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte b[] = md.digest();

            str = byteToStr(b);
        } catch (Exception e) {
    
            e.printStackTrace();

        }
        return str;
    }

    public static String byteToStr(byte[] b){
    
        int i;
        StringBuffer buf = new StringBuffer("");
        for (int offset = 0; offset < b.length; offset++) {
    
            i = b[offset];
            //System.out.println(i);
            if (i < 0)
                i += 256;
            if (i < 16)
                buf.append("0");
            buf.append(Integer.toHexString(i));
        }
        return buf.toString();
    }

    /**
     * 传入文本内容,返回 SHA-256 串
     *
     * @param strText
     * @return
     */
    public static String SHA256(final String strText)
    {
    
        return SHA(strText, "SHA-256");
    }

    public static String SHA1(final String strText)
    {
    
        return SHA(strText, "SHA-1");
    }

    /**
     * 传入文本内容,返回 SHA-512 串
     *
     * @param strText
     * @return
     */
    public static String SHA512(final String strText)
    {
    
        return SHA(strText, "SHA-512");
    }

    /**
     * 字符串 SHA 加密
     *
     * @param strText
     * @return
     */
    private static String SHA(final String strText, final String strType)
    {
    
        // 返回值
        String strResult = null;

        // 是否是有效字符串
        if (strText != null && strText.length() > 0)
        {
    
            try
            {
    
                // SHA 加密开始
                MessageDigest messageDigest = MessageDigest.getInstance(strType);
                // 传入要加密的字符串
                messageDigest.update(strText.getBytes("utf-8"));
                // 得到 byte 类型的结果
                byte byteBuffer[] = messageDigest.digest();
                strResult = byteToStr(byteBuffer);
            }
            catch (NoSuchAlgorithmException e)
            {
    
                e.printStackTrace();
            }catch (UnsupportedEncodingException e) {
    
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

        return strResult;
    }

    public static String base64(String str){
    
        String baseStr = null;
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] textByte;
        try {
    
            textByte = str.getBytes("UTF-8");
            baseStr = encoder.encodeToString(textByte);
        } catch (UnsupportedEncodingException e) {
    
            e.printStackTrace();
        }

        return baseStr;

    }

    public static void main(String[] args) {
    
        String password = "bunana1";
        System.out.println(md5(password));
        //String base64 = base64(sha512);
        //System.out.println(base64);
        //String pwd1 = md5(base64);
        //System.out.println(pwd1);
    }
}

测试

测试采取ApiPost 工具,让测试更接近前后分离。

首先测试登录

Post
localhost/user/login

  • 账号密码有一个不对时。
    在这里插入图片描述
  • 正确的账号密码
    在这里插入图片描述
    在这里插入图片描述

测试存在权限的接口

localhost/user/test1

在这里插入图片描述
在这里插入图片描述

测试不存在权限的接口

localhost/user/test2

在这里插入图片描述
在这里插入图片描述

测试登出

localhost/logouts

在这里插入图片描述

测试不需要权限的接口

localhost/test

在这里插入图片描述

数据库sql脚本

CREATE TABLE `user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, -- 主键
  `username` varchar(255) DEFAULT NULL,    -- 用户名
  `password` varchar(255) DEFAULT NULL,    -- 用户密码
  `enabled` tinyint(1) DEFAULT '1',        -- 是否启用 1-启用 0-未启用
  `locked` tinyint(1) DEFAULT '0',         -- 是否被锁 1-已锁 0-未锁
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

数据为:


insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS","1babad058e03c5296a94a5a8d7d6dd8a",1,0); -- bunana 的md5 值
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS2","0b13310f8db2dc22e7ddd0cdc5f0a61a",1,0); -- bunana1 的md5 值
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS3","b3fbcd9c9d97e47f263a19a0e01efc7d",1,0); -- bunana2 的md5 值

在这里插入图片描述

个人对框架的见解

针对登录登出操作,采取编写filter过滤器的方式进行逻辑编写,开发者不需要写具体的Controller。

只是在比对上,框架做了自己的判断。

针对其他接口的权限校验,也是各种过滤器中的使用。

由security底层逻辑根据定义的接口的权限,和读取到的角色的权限信息进行对比判断校验。

代码下载

springboot-security-10-qianhou

gitee 代码下载地址

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_38322527/article/details/123121463

智能推荐

使用JDBC连接数据库出现 The server time zone value ‘�й���׼ʱ��‘ is unrecognized or represents more than one解决方案_jdbc.properties timezone-程序员宅基地

文章浏览阅读553次。在 jdbc.properties 文件中的 url 后面加上 ?serverTimezone=UTC加入之前的jdbc.properties文件:user=rootpassword=12345678url=jdbc:mysql://localhost:3306/testdriverClass=com.mysql.cj.jdbc.Driver加入之后:user=rootpassword=12345678url=jdbc:mysql://localhost:3306/test?serv_jdbc.properties timezone

计算机图形学孔令德基础知识,计算机图形学基础教程孔令德答案-程序员宅基地

文章浏览阅读1.4k次。计算机图形学基础教程孔令德答案【篇一:大学计算机图形学课程设】息科学与工程学院课程设计任务书题目:小组成员:巴春华、焦国栋成员学号:专业班级:计算机科学与技术、2009级本2班课程:计算机图形学指导教师:燕孝飞职称:讲师完成时间: 2011年12 月----2011年 12 月枣庄学院信息科学与工程学院制2011年12 月20日课程设计任务书及成绩评定12【篇二:计算机动画】第一篇《计算机图形学》..._计算机图形学基础教程 孔令德 答案

python xlwings追加数据_大数据分析Python库xlwings提升Excel工作效率教程-程序员宅基地

文章浏览阅读1k次。原标题:大数据分析Python库xlwings提升Excel工作效率教程Excel在当今的企业中非常非常普遍。在AAA教育,我们通常建议出于很多原因使用代码,并且我们的许多数据科学课程旨在教授数据分析和数据科学的有效编码。但是,无论您偏爱使用大数据分析Python的程度如何,最终,有时都需要使用Excel来展示您的发现或共享数据。但这并不意味着仍然无法享受大数据分析Python的某些效率!实际上,..._xlwings通过索引添加数据

java8u211_jre864位u211-程序员宅基地

文章浏览阅读911次。iefans为用户提供的jre8 64位是针对64位windows平台而开发的java运行环境软件,全称为java se runtime environment 8,包括Java虚拟机、Java核心类库和支持文件,不包含开发工具--编译器、调试器和其它工具。jre需要辅助软件--JavaPlug-in--以便在浏览器中运行applet。本次小编带来的是jre8 64位官方版下载,版本小号u211版..._jre8是什么

kasp技术原理_KASP基因分型-程序员宅基地

文章浏览阅读5k次。KASP基因分型介绍KASP(Kompetitive Allele-Specific PCR),即竞争性等位基因特异性PCR,原理上与TaqMan检测法类似,都是基于终端荧光信号的读取判断,每孔反应都是采用双色荧光检测一个SNP位点的两种基因型,不同的SNP对应着不同的荧光信号。KASP技术与TaqMan法类似,它与TaqMan技术不同的是,它不需要每个SNP位点都合成特异的荧光引物,它基于独特的..._kasp是什么

华为p50预装鸿蒙系统,华为p50会不会预装鸿蒙系统_华为p50会预装鸿蒙系统吗-程序员宅基地

文章浏览阅读154次。华为现在比较火的还真就是新开发的鸿蒙系统了,那么在即将上市的华为p50手机上会不会预装鸿蒙系统呢?接下来我们就来一起了解一下华为官方发布的最新消息吧。1.华为p50最新消息相信大家都知道,随着华为鸿蒙OS系统转正日期临近,似乎全网的花粉们都在关注华为鸿蒙OS系统优化、生态建设等等,直接忽略了不断延期发布的华为P50手机,如今华为P50系列手机终于传来了最新的好消息,在经过一系列方案修改以后,终于被..._华为手机p50直接预装鸿蒙系统

随便推点

python用什么软件编程好-初学python编程,有哪些不错的软件值得一用?-程序员宅基地

文章浏览阅读2.1k次。Python编程的软件其实许多,作为一门面向大众的编程言语,许多修正器都有对应的Python插件,当然,也有特地的PythonIDE软件,下面我简单引见几个不错的Python编程软件,既有修正器,也有IDE,感兴味的朋友可以本人下载查验一下:1.VSCode:这是一个轻量级的代码修正器,由微软规划研发,免费、开源、跨途径,轻盈活络,界面精练,支撑常见的自动补全、语法提示、代码高亮、Git等功用,插..._python入门学什么好

pytorch一步一步在VGG16上训练自己的数据集_torch vgg训练自己的数据集-程序员宅基地

文章浏览阅读3.2w次,点赞30次,收藏307次。准备数据集及加载,ImageFolder在很多机器学习或者深度学习的任务中,往往我们要提供自己的图片。也就是说我们的数据集不是预先处理好的,像mnist,cifar10等它已经给你处理好了,更多的是原始的图片。比如我们以猫狗分类为例。在data文件下,有两个分别为train和val的文件夹。然后train下是cat和dog两个文件夹,里面存的是自己的图片数据,val文件夹同train。这样我们的..._torch vgg训练自己的数据集

毕业论文管理系统设计与实现(论文+源码)_kaic_论文系统设计法-程序员宅基地

文章浏览阅读968次。论文+系统+远程调试+重复率低+二次开发+毕业设计_论文系统设计法

在python2与python3中转义字符_Python 炫技操作:五种 Python 转义表示法-程序员宅基地

文章浏览阅读134次。1. 为什么要有转义?ASCII 表中一共有 128 个字符。这里面有我们非常熟悉的字母、数字、标点符号,这些都可以从我们的键盘中输出。除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上的找到,比如制表符、响铃这种。为了能将那些特殊字符都能写入到字符串变量中,就规定了一个用于转义的字符 \ ,有了这个字符,你在字符串中看的字符,print 出来后就不一定你原来看到的了。举个例子>..._pytyhon2、python3对%转义吗

java jar 文件 路径问题_「问答」解决jar包运行时相对路径问题-程序员宅基地

文章浏览阅读1.3k次。我这几天需要做一个Java程序,需要通过jar的形式运行,还要生成文件。最终这个程序是要给被人用的,可能那个用的人还不懂代码。于是我面临一个问题:生成的文件一定不能存绝对路径。刚开始我想得很简单,打绝对路径改成相对路径不就行了吗?于是有了这样的代码:String path = "../test.txt";File file = new File(path);……这个写法本身并没有问题,直接运行代码..._jar启动文件路径中存在!

微信读书vscode插件_曾经我以为 VSCode 是程序员专属的工具,直到发现了这些……...-程序员宅基地

文章浏览阅读598次。如果你知道 VSCode,一说起它,你可能第一个想到的就是把它当做一个代码编辑器,而它的界面应该可能大概率是这样的——如果你恰好又是个程序员,那你可能经常会用到它,不管是 Python、JS 还是 C++ 等各种语言对应的文件,都可以用它来进行简单的编辑和整理,甚至是运行和 debug......但是今天要讲的显然不是这些,经过小美的多方研究,发现了即使是对于大多数并不了解 VSCode,也完全不..._vscode weixin read

推荐文章

热门文章

相关标签