springboot+springsecurity+JWT整合restful服务(三)

我们知道,在restful服务中,服务端不再直接生成页面了,而是只返回数据(json),客户端渲染,

所以我们需要定义数据格式

修改之前项目,之前已经引入过JWT了所以无需再引

通过之前文章,我们知道配置的重点是自定义
UserDetailsService

通过百度我们知道是UsernamePasswordAuthenticationFilter帮我们认证了用户,并且生成了页面,转发等等。

但是!在本例中,因为我们是整合restful服务,返回的都是json数据,所以我们不再需要这些。而既然要返回json数据,那我们先定义好一个通用的数据类,ResultVO:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultVO<T> {
    //10成功,20需要登陆  30无权限等等
    private Integer code;
    private String msg;
    private T data;
}

首先写一个JwtUtils工具

package com.allmodel.models.authority.config.security.utils;

import com.allmodel.models.authority.config.security.entity.AuthUser;
import com.allmodel.models.authority.entity.RoleEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author WQY
 * @Date 2019/11/26 15:39
 * @Version 1.0
 */
public class JwtUtil {
    private static final String secret = "wlzl.com";

    /**
     * 生成token
     * @param username
     * @param roles
     * @return
     */
    public static String generateToken(String username,List<RoleEntity> roles) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", roles.parallelStream().map(RoleEntity::getName).collect(Collectors.joining(",")));

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                //.setAudience(username+".")
                //创建时间
                .setIssuedAt(new Date())
                //过期时间,我们设置为 五分钟
                .setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
                //签名,通过密钥保证安全性
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 解析token
     * @param token
     * @return
     */
    public static AuthUser parseToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
        String username = claims.getSubject();
        //String audience = claims.getAudience();
        //System.out.println(audience);
        String roles = (String) claims.get("roles");

        //因为生成的时候没有放入密码,所以不需要密码
        return new AuthUser(username, null, Arrays.stream(roles.split(",")).map(name -> {
            RoleEntity role = new RoleEntity();
            role.setName(name);
            return role;
        }).collect(Collectors.toList()));
    }
}

然后是获取生成token的controller

package com.allmodel.models.authority.controller;

import com.allmodel.models.authority.config.security.entity.ResultVO;
import com.allmodel.models.authority.config.security.utils.JwtUtil;
import com.allmodel.models.authority.dao.Role_Jpa;
import com.allmodel.models.authority.dao.User_Jpa;
import com.allmodel.models.authority.entity.RoleEntity;
import com.allmodel.models.authority.entity.UserEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

/**
 * @Author WQY
 * @Date 2019/11/26 15:42
 * @Version 1.0
 */
@RestController
public class UserController {
    @Resource
    private User_Jpa userRepository;
    @Resource
    private Role_Jpa roleRepository;

    @GetMapping("/token")
    public ResultVO login(String username, String password) {
        UserEntity user = userRepository.findByUsername(username);

        if (user == null || !user.getPassword().equals(password)) {
            ResultVO<Object> result = new ResultVO<>();
            result.setCode(10);
            result.setMsg("用户名或密码错误");
            return result;
        }

        //Jwts.
        ResultVO<Object> success = new ResultVO<>();
        //用户名密码正确,生成token给客户端
        success.setCode(0);
        List<RoleEntity> roles = Collections.singletonList(roleRepository.findById(user.getAuthority()).get());
        success.setData(JwtUtil.generateToken(username, roles));

        return success;
    }
}

我们定义两个异常处理,一个是token异常,一个是权限异常(就是有没有权限)

package com.allmodel.models.authority.config.security.handler;

import com.allmodel.models.authority.config.security.entity.ResultVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author WQY
 * @Date 2019/11/26 15:43
 * @Version 1.0
 */
@Component
public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        // 返回我们的自定义json
        ObjectMapper objectMapper = new ObjectMapper();
        ResultVO<Object> result = new ResultVO<>();
        //50,标识有token,但是该用户没有权限
        result.setCode(50);
        result.setMsg("请求无效,用户没有权限");
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}
package com.allmodel.models.authority.config.security.handler;

import com.allmodel.models.authority.config.security.entity.ResultVO;
import com.fasterxml.jackson.databind.ObjectMapper;
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;

/**
 * @Author WQY
 * @Date 2019/11/26 15:43
 * @Version 1.0
 */
@Component
public class TokenExceptionHandler implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        // 直接返回 json错误
        ResultVO<Object> result = new ResultVO<>();
        //20,标识没有token
        result.setCode(20);
        result.setMsg("请求无效,没有有效token");
        ObjectMapper objectMapper = new ObjectMapper();
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}

然后通过百度得知,在UsernamePasswordAuthenticationFilter中,springsecurity加入了SecurityContext,既然我们现在不用了,那我们要自己定义拦截器并且加入
securityContext以便springsecurity作权限处理,所以我们自定义拦截器:

package com.allmodel.models.authority.config.security.filter;

import com.allmodel.models.authority.config.security.entity.AuthUser;
import com.allmodel.models.authority.config.security.utils.JwtUtil;
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;

/**
 * @Author WQY
 * @Date 2019/11/26 15:44
 * @Version 1.0
 */
@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String token = request.getHeader("token");

        //获取token,并且解析token,如果解析成功,则放入 SecurityContext
        if (token != null) {
            try {
                AuthUser authUser = JwtUtil.parseToken(token);
                //如果此处不放心解析出来的 authuser,可以再从数据库查一次,验证用户身份:
                //解析成功
                if (SecurityContextHolder.getContext().getAuthentication() == null) {
                    //我们依然使用原来filter中的token对象
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());

                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            } catch (Exception e) {
                logger.info("解析失败,可能是伪造的或者该token已经失效了(我们设置失效5分钟)。");
            }
        }

        filterChain.doFilter(request, response);
    }
}

然后修改

SecurityConfig

由于我集成了Swagger2所以我把Swagger2的相关东西开放了

package com.allmodel.models.authority.config.security;

import com.allmodel.models.authority.config.security.filter.JwtTokenFilter;
import com.allmodel.models.authority.config.security.handler.AccessDeniedHandler;
import com.allmodel.models.authority.config.security.handler.TokenExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * @Author WQY
 * @Date 2019/11/26 11:12
 * @Version 1.0
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private TokenExceptionHandler tokenExceptionHandler;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Resource
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 因为我们的token是无状态的,不需要跨站保护
                .csrf().disable()
                // 添加异常处理,以及访问禁止(无权限)处理
                .exceptionHandling().authenticationEntryPoint(tokenExceptionHandler).accessDeniedHandler(accessDeniedHandler).and()

                // 我们不再需要session了
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                //定义拦截页面,所有api全部需要认证
                .authorizeRequests()

                .anyRequest().authenticated();

        //最后,我们定义 filter,用来替换原来的UsernamePasswordAuthenticationFilter
        httpSecurity.addFilterAt(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                // 让我们获取 token的api不走springsecurity的过滤器,大道开放
                .antMatchers(HttpMethod.GET,
                        "/token",
                        "/webjars/**",
                        "/resources/**",
                        "/swagger-ui.html",
                        "/swagger-resources/**",
                        "/v2/api-docs"
                        );
    }
}
//@Slf4j
//@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
//public class SecurityConfig extends WebSecurityConfigurerAdapter {
//
//    private Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
//
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
//        http
//                .formLogin()
//                .loginPage("/login")
//                .loginProcessingUrl("/login")//处理登录请求接口
//                .successForwardUrl("/hello")
//                .defaultSuccessUrl("/hello",true)
//                .and()
//                .authorizeRequests()
//                .antMatchers("/index","/login","/error").permitAll()
//                .anyRequest()
//                .authenticated()
//                .and()
//                .csrf()
//                .disable();;
//
        http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
                .antMatchers("/login","/error","/index").permitAll()// 设置所有人都可以访问登录页面
                .anyRequest().authenticated()  // 任何请求,登录后可以访问
                .and()
                .formLogin().loginPage("/login");
        ;
//    }
//}

然后写一个controller进行测试

@PreAuthorize("hasRole('ROLE_ADMIN')")此处就是什么什么权限的用户进行访问

最后我会把sql放上

ROLE_ADMIN中,ROLE_是Security默认添加的,数据库表中的是admin

package com.allmodel.models.authority.controller;

import com.allmodel.models.authority.config.security.entity.AuthUser;
import com.allmodel.models.authority.config.security.entity.ResultVO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author WQY
 * @Date 2019/11/26 15:45
 * @Version 1.0
 */
@RestController
@RequestMapping
public class PermissionController {

    @GetMapping("/permission")
    public ResultVO loginTest(@AuthenticationPrincipal AuthUser authUser) {
        ResultVO<String> resultVO = new ResultVO<>();
        resultVO.setCode(0);

        resultVO.setData("你成功访问了该api,这代表你已经登录,你是: " + authUser);
        return resultVO;
    }

    @GetMapping("/permission2")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public ResultVO loginTest() {
        ResultVO<String> resultVO = new ResultVO<>();
        resultVO.setCode(0);

        resultVO.setData("你成功访问了需要有 admin 角色的api。");
        return resultVO;
    }
}

然后我们访问hello,因为我们没有获取token切传入token所以会出现20

我们先获取token

用户名密码错误

密码不对,因为数据库现在放的是之前版本的密码,是加密过的

所以现在要把数据库中的加密密码改成123456

然后在登陆

就获取到token了

 然后访问/permission

因为下面访问带消息头,所以我用的postman测试

由于我随便在修改了token所以现在token无效的

然后把刚刚获取的token放进去

 然后访问/permission2

由于该账号具有admin权限所以可以访问

然后我们换一个没有权限的用户

先获取这个用户的token

然后访问

 

到此就整合完成了。

sql文件:

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50717
 Source Host           : localhost:3306
 Source Schema         : authority

 Target Server Type    : MySQL
 Target Server Version : 50717
 File Encoding         : 65001

 Date: 27/11/2019 12:18:28
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_ADMIN');
INSERT INTO `role` VALUES (2, 'ROLE_USER');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `savetime` datetime(0) NULL DEFAULT NULL COMMENT '保存时间',
  `iphone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `authority` int(2) NULL DEFAULT NULL COMMENT '权限,1是最大权限,2是最小权限',
  `sex` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别,1男,2女',
  `organization_num` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组织机构编号',
  `organization_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组织机构名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '123456', '2019-09-09 14:40:50', '15373065417', 1, '1', '1000', '物联智略');
INSERT INTO `user` VALUES (10, 'wqy', '123456', NULL, '', 2, '男', '10000007', '武邑环保局');
INSERT INTO `user` VALUES (11, '123', '321', '2019-11-25 12:37:35', NULL, 2, '312', '231', '312');

SET FOREIGN_KEY_CHECKS = 1;

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot和Spring Security可以很好地结合使用来实现RESTful API的认证。而JWT(JSON Web Token)是一种用于认证和授权的安全传输方式。 要在Spring Boot中实现JWT认证,可以遵循以下步骤: 1. 添加依赖:在`pom.xml`文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建JWT工具类:创建一个JWT工具类来生成和解析JWT。可以使用JJWT库来简化这个过程。 3. 创建认证过滤器:创建一个继承自`OncePerRequestFilter`的认证过滤器,在该过滤器中检查请求中的JWT,并进行认证。 4. 配置Spring Security:将认证过滤器添加到Spring Security的配置中,以便在每个请求到达之前进行JWT认证。 5. 创建登录接口:创建一个登录接口,用于验证用户的身份并生成JWT。 这是一个简单的示例代码,说明如何在Spring Boot中实现JWT认证: ```java // JWT工具类 public class JwtUtils { private static final String SECRET_KEY = "your-secret-key"; private static final long EXPIRATION_TIME = 864_000_000; // 10天 public static String generateToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME); return Jwts.builder() .setSubject(userPrincipal.getUsername()) .setIssuedAt(new Date()) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) { return false; } } } // 认证过滤器 public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authorizationHeader = request.getHeader("Authorization"); if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) { String token = authorizationHeader.substring(7); if (jwtUtils.validateToken(token)) { String username = jwtUtils.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } } // Spring Security配置类 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } // 登录接口 @RestController public class AuthController { @Autowired private AuthenticationManager authenticationManager; @PostMapping("/login") public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); String token = JwtUtils.generateToken(authentication); return ResponseEntity.ok(new JwtResponse(token)); } } // 登录请求DTO public class LoginRequest { private String username; private String password; // getters and setters } // JWT响应DTO public class JwtResponse { private String token; // constructor and getter } // 用户详情实现类 @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username)); return UserDetailsImpl.build(user); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值