SpringSecurity+JWT使用步骤及心得

1、添加相关依赖

主要是springsecurity和jwt依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.njwj</groupId>
<artifactId>gx</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gx</name>
<description>guanxian</description>
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <!-- json转换依赖-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
    </dependency>

    <!--springsecurity依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--jwt依赖-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>

    <dependency>
        <groupId>com.oracle.database.jdbc</groupId>
        <artifactId>ojdbc8</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--        通用mapper的依赖-->
    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper-spring-boot-starter</artifactId>
        <version>2.0.4</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.6.4</version>
        </plugin>
    </plugins>
</build>
</project>

2、配置数据库和myabtis

这里就不一一赘述了

3、User类、UserMapper、UserService、UserServiceImpl

3.1 User类

@Data
@Table(name = "USER_S")
public class User implements Serializable {
    private static final long serialVersionUID = 2L;
    @Id
    @Column(name = "USER_S_ID")
    private Integer userId;
    @Column(name = "USER_S_NAME")
    private String userName;
    @Column(name = "USER_S_PASS")
    private String userPassword;
    @Column(name = "USER_S_CODE")
    private String userCode;
    @Column(name = "USER_S_SEX")
    private String userSex;
    @Column(name = "USER_S_PHONE")
    private String userPhone;
    @Column(name = "USER_S_STATUS")
    private String userStatus;
    @Column(name = "USER_S_IN")
    private Date userIn;
    @Column(name = "USER_S_SKILL")
    private String userSkill;
    @Column(name = "USER_S_LEVEL")
    private String userLevel;
    @Column(name = "USER_S_W_STATUS")
    private String userWStatus;
}

3.2 UserMapper

这里使用了通用Mapper

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {

}

3.3 UserService

public interface UserService {
    Map<String, String> addUser(User user);

    User findByUserName(String username);

    List<User> findAll();

    Map<String,String> deleteUser(Integer id);
}

3.4 UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public Map<String, String> addUser(User user) {
        Map<String, String> addMap = new HashMap<>();
        if (userMapper.insert(user)>0) {
            addMap.put("result", "true");
            addMap.put("msg", "添加成功");
        } else {
            addMap.put("result", "false");
            addMap.put("msg", "添加失败");
        }
        return addMap;
    }

    @Override
    public User findByUserName(String username) {
        User user = new User();
        user.setUserName(username);
        return userMapper.selectOne(user);
    }

    @Override
    public List<User> findAll() {
        return userMapper.selectAll();
    }

    @Override
    public Map<String, String> deleteUser(Integer id) {
        Map<String, String> delMap = new HashMap<>();
        if (userMapper.deleteByPrimaryKey(id)>0) {
            delMap.put("result", "true");
            delMap.put("msg", "删除成功");
        } else {
            delMap.put("result", "false");
            delMap.put("msg", "删除失败");
        }
        return delMap;
    }
}

4、创建JWT工具类

用于管理token操作

public class JWTUtil {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer";

    // TOKEN 过期时间
    public static final long EXPIRATION = 1000 * 60 * 30; // 三十分钟

    public static final String APP_SECRET_KEY = "secret";

    private static final String ROLE_CLAIMS = "rol";

    /**
     * 生成token
     *
     * @param username
     * @param role
     * @return
     */
    public static String createToken(String username, String role) {

        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);

        String token = Jwts
                .builder()
                .setSubject(username)
                .setClaims(map)
                .claim("username", username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, APP_SECRET_KEY).compact();
        return token;
    }

    /**
     * 获取当前登录用户用户名
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 获取当前登录用户角色
     *
     * @param token
     * @return
     */
    public static String getUserRole(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("rol").toString();
    }

    /**
     * 解析token中的信息
     *
     * @param token
     * @return
     */
    public static Claims checkJWT(String token) {
        try {
            final Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检查token是否过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
}

5、创建JwtUser类

主要用于封装登录用户相关信息,例如用户名,密码,权限集合等,必须实现UserDetails接口

public class JWTUser implements UserDetails {

    private Integer userId;
    private String userName;
    private String userPassword;
    private Collection<? extends GrantedAuthority> authorities;

    public JWTUser(){

    }

    public JWTUser(User user) {
        userId = user.getUserId();
        userName = user.getUserName();
        userPassword = user.getUserPassword();
        authorities = Collections.singleton(new SimpleGrantedAuthority(user.getUserLevel()));
    }


    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public String getPassword() {
        return userPassword;
    }

    public String getUsername() {
        return userName;
    }

    public boolean isAccountNonExpired() {
        return true;
    }

    public boolean isAccountNonLocked() {
        return true;
    }

    public boolean isCredentialsNonExpired() {
        return true;
    }

    public boolean isEnabled() {
        return true;
    }
}

6、创建JwtUserService

必须实现UserDetailsService,重写loadUserByUsername()方法

@Service
public class JWTUserService implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByUserName(username);
        if (user != null){
            JWTUser jwtUser = new JWTUser(user);
            return jwtUser;
        }else {
            try {
                throw new ValidationException("该用户不存在");
            }catch (ValidationException e){
                e.printStackTrace();
            }
        }
        return null;
    }
}

7、配置认证拦截器

拦截用户登录信息

/**
 *
 * 验证用户名密码正确后,生成一个token,并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法
 * attemptAuthentication:接收并解析用户凭证。
 * successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
 *
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    /**
     * 拦截登录请求
     * @param authenticationManager
     */
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
       // super.setFilterProcessesUrl("/login");

    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        // 从输入流中获取到登录的信息
        try {
            User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getUserPassword())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        JWTUser jwtUser = (JWTUser) authResult.getPrincipal();

        String role = "";
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();
        }
        //生成token
        String token = JWTUtil.createToken(jwtUser.getUsername(), role);
        // 返回创建成功的token  但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        String tokenStr = JWTUtil.TOKEN_PREFIX + token;
        response.setHeader("token", tokenStr);

        ResultInfo resultInfo = new ResultInfo();
        response.setStatus(200);
        PrintWriter writer = response.getWriter();
        resultInfo.setResult(true);
        resultInfo.setMsg("登录成功");
        ObjectMapper mapper = new ObjectMapper();
        String resultJson = mapper.writeValueAsString(resultInfo);
        writer.write(resultJson);
        writer.flush();
        writer.close();
    }

    // 失败 返回错误就行
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setStatus(401);
        PrintWriter writer = response.getWriter();
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setResult(false);
        resultInfo.setMsg("用户名或密码不正确,请重新登录");
        ObjectMapper mapper = new ObjectMapper();
        String resultJson = mapper.writeValueAsString(resultInfo);
        writer.write(resultJson);
        writer.flush();
        writer.close();
    }
}

8、配置权限拦截器

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

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

        String tokenHeader = request.getHeader(JWTUtil.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JWTUtil.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token 就是上面说的设置认证信息
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {

        String token = tokenHeader.replace(JWTUtil.TOKEN_PREFIX, "");
        // 检测token是否过期 如果过期会自动抛出错误
        JWTUtil.isExpiration(token);
        String username = JWTUtil.getUsername(token);
        String role = JWTUtil.getUserRole(token);
        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null,
                    Collections.singleton(new SimpleGrantedAuthority(role))
            );
        }
        return null;
    }
}

9、配置SecurityConfig文件

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private  JWTUserService jwtUserService;

    @Autowired
    private UrlLogoutSuccessHandler logoutSuccessHandler;

    @Autowired
    private UrlAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private UrlAuthenticationEntryPoint authenticationEntryPoint;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

/**
 * 这边 通过重写configure(),去数据库查询用户是否存在
 * @param auth
 * @throws Exception
 */

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtUserService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager())) // 用户登录拦截
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))  // 权限拦截
                // 不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 以/user 开头的请求 都需要进行验证
                .antMatchers("/user/**")
                .authenticated()
                // 其他都放行了
                .anyRequest().permitAll()
                //.anyRequest().authenticated()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .clearAuthentication(true)
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

10、UserController

@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMapping(value = "/addUser")
    public @ResponseBody ResultInfo addUser(@RequestBody User user){
        user.setUserPassword(bCryptPasswordEncoder.encode(user.getUserPassword()));
        Map<String,String> resultMap = userService.addUser(user);
        ResultInfo<DownloadAreaRecordData> resultInfo = new ResultInfo<>();
        if (Boolean.valueOf(resultMap.get("result"))){
            resultInfo.setResult(true);
            resultInfo.setMsg(resultMap.get("msg"));
        }else{
            resultInfo.setResult(false);
            resultInfo.setMsg(resultMap.get("msg"));
        }
        return resultInfo;
    }

    @GetMapping
    public User findByUserName(String userName){
        return userService.findByUserName(userName);
    }

    @GetMapping("/findAll")
    @ResponseBody
    @PreAuthorize("hasAnyAuthority('admin')")
    public ResultInfo findAll(){
        ResultInfo<User> resultInfo = new ResultInfo<>();
        List<User> list = userService.findAll();
        if (list!=null&&list.size()>0){
            resultInfo.setResult(true);
            resultInfo.setMsg("success");
            resultInfo.setList(list);
        }else{
            resultInfo.setResult(false);
            resultInfo.setMsg("failure");
            resultInfo.setList(null);
        }
        return resultInfo;
    }

    @GetMapping("/delUser")
    public @ResponseBody ResultInfo delUser(@RequestParam Integer id){
        Map<String,String> resultMap = userService.deleteUser(id);
        ResultInfo<DownloadAreaRecordData> delResultInfo = new ResultInfo<>();
        if (Boolean.valueOf(resultMap.get("result"))){
            delResultInfo.setResult(true);
            delResultInfo.setMsg(resultMap.get("msg"));
        }else{
            delResultInfo.setResult(false);
            delResultInfo.setMsg(resultMap.get("msg"));
        }
        return delResultInfo;
    }
}

11、最后配置handler

主要考虑到前后端分离,传递给前端json字符串
11.1 无权访问的AccessDeniedHandler

@Component
public class UrlAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResultInfo resultInfo = new ResultInfo();
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        resultInfo.setResult(false);
        resultInfo.setMsg("对不起,您无权访问");
        ObjectMapper mapper = new ObjectMapper();
        String resultJson = mapper.writeValueAsString(resultInfo);
        writer.write(resultJson);
        writer.flush();
        writer.close();
    }
}

11.2 未登录就访问的AuthencationEntryPoint

@Component
public class UrlAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResultInfo resultInfo = new ResultInfo();
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        resultInfo.setResult(false);
        resultInfo.setMsg("未登录,请先登录");
        ObjectMapper mapper = new ObjectMapper();
        String resultJson = mapper.writeValueAsString(resultInfo);
        writer.write(resultJson);
        writer.flush();
        writer.close();
    }
}

11.3 注销成功的LogoutSuccessHandler

@Component
public class UrlLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        ResultInfo resultInfo = new ResultInfo();
        response.setCharacterEncoding("UTF-8");
        response.setStatus(200);
        PrintWriter writer = response.getWriter();
        resultInfo.setResult(true);
        resultInfo.setMsg("注销成功");
        ObjectMapper mapper = new ObjectMapper();
        String resultJson = mapper.writeValueAsString(resultInfo);
        writer.write(resultJson);
        writer.flush();
        writer.close();
    }
}

引用借鉴https://blog.csdn.net/weixin_45452416/article/details/109528425

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值