草鸡的SpringSecuity+JWT

背景:最近在搭建这玩意儿,本来就毕设那会用了一下,还是我自己全栈,也不需要前后端分离,但是现在不行了,前后端分离了。而且这边还有账号登录验证和短信验证码验证,草鸡累了。

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/teach/common/*").permitAll().anyRequest().authenticated()
                .antMatchers("/teach/courseware/*").hasAuthority("teacher")
//                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().addFilter(new JWTAuthenticationFilter(authenticationManager())).
                addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .addFilter(new JWTSAMAuthenticationFilter(authenticationManager()))
                .exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .accessDeniedHandler(new jwtAccessDeniedHandler())
                .and().logout()
                .and().csrf().disable();
        http.cors().disable();
    }

首先这是重写WebSecurityConfigurerAdapterd,来加入新加的JWT用户名登录和短信验证码登录的登录成功生成token的过滤器和一个鉴权的过滤器。

MyDetailService

@Component
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    UserService userService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (null==username || "".equals(username)){
            return null;
        }
        UserDTO userDTO = userService.findUserInfoByUsername(username);
        if (null==userDTO){
            return null;
        }
        List<GrantedAuthority> authorities=new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority(userDTO.getRole()));
        return new UserInfo(userDTO.getId(),userDTO.getUsername(),userDTO.getPassword(),authorities);
    }
}

自定义了一个detailservice,做这个第一当然是从数据库中动态验证用户信息,第二用户的id也封装进去了,毕竟后期真的写代码,还是用户id用的比较广泛。

UserInfo

package com.ccas.common.entity;

import io.swagger.models.auth.In;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

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


public class UserInfo extends User {

    private Integer id;

    private String username;

    private String password;

    private String role;



    public UserInfo(Integer id,String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.id=id;
    }

    public UserInfo(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public Integer getId() {
        return id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

这边重写了Spring security的user类,毕竟从上可以看到原始的这个类只有用户,密码和权限组,这边把id和单个角色放进去了。

JWTAuthenticationFilter

package com.ccas.common.jwt;

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    @Autowired
    UserService userService;


    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        setAuthenticationManager(authenticationManager);
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/teach/common/login");

        System.out.println("filter==="+authenticationManager+"---");
    }


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

        // 从输入流中获取到登录的信息

        try {
            JSONObject jsonObject = new JSONObject();  //   前端 发送 json 对象  username  password
            JWTUser loginUser  = jsonObject.parseObject(request.getInputStream(), JWTUser.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
            );
        } catch (Exception e) {
            try {
                Result info = new Result();
                info.setCode(500);
                info.setMessage("账号或密码认证失败");
                response.setContentType("text/json;charset=utf-8");
                String s = JSONObject.toJSONString(info);
                response.getWriter().print(s);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    public void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        //  认证成功 我们需要将用户信息 生成token   发送给客户端
        UserInfo userInfo = (UserInfo) authResult.getPrincipal();
        Collection<GrantedAuthority> authorities = userInfo.getAuthorities();
        GrantedAuthority[] objects = authorities.toArray(new GrantedAuthority[]{});
        StringBuffer sb = new StringBuffer();
        for (int i=0;i<authorities.size();i++){
            String authority = objects[i].getAuthority();
            if(i==authorities.size()-1){
                sb.append(authority) ;
            }else{
                sb.append(authority+"-") ;
            }
        }
        userInfo.setRole(sb.toString());

        String token = null;
        response.setContentType("application/json; charset=utf-8");
        Result<Object> errorInfoDTO = new Result();
        try {

            token = JwtUtils.generateToken(userInfo);
            String tokenStr =  JwtUtils.TOKEN_PREFIX+ token;
            Map<String,Object> map=new HashMap<>();
            map.put("token",tokenStr);
            map.put("role",sb.toString());
            response.setHeader(JwtUtils.TOKEN_NAME,tokenStr); //  header形式 发送给客户端浏览器
            errorInfoDTO.setCode(200);
            errorInfoDTO.setMessage("登录成功");
            errorInfoDTO.setData(map);
        } catch (Exception e) {
            e.printStackTrace();
            errorInfoDTO.setCode(500);
            errorInfoDTO.setMessage("token生成失败");
        }
        String s = JSONObject.toJSONString(errorInfoDTO);
        response.getWriter().print(s);
    }

    @Override
    public  void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setStatus(403);
        response.setContentType("application/json; charset=utf-8");
        if (failed instanceof BadCredentialsException) {
            response.getWriter().write("authentication failed, reason: 密码错误"  );
        }else{
            response.getWriter().write("authentication failed, reason: " + failed.getMessage());
        }
    }
}

      这是用户名密码验证。默认的登录的url是login,所以这边重新定义了一个新的登录url。从前端发送过来的json对象,用一个user对象去接收到,并放入默认的UsernamePasswordAuthenticationToken去进行密码校验,要是成功登录就通过JWTUtils去生成相对应的token。

    public static String generateToken(UserInfo userInfo) throws Exception {
        return Jwts.builder()
                .claim(JwtConstants.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstants.JWT_KEY_USER_NAME, userInfo.getUsername())
                .claim(JwtConstants.JWT_KEY_USER_ROLE, userInfo.getRole())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)
                .compact();
    }

 接下来就是鉴权了。

package com.ccas.common.jwt;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

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

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

        String tokenHeader = request.getHeader(JwtUtils.TOKEN_NAME);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
        }else{
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader,response));
            super.doFilterInternal(request, response, chain);
        }

    }

    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader,HttpServletResponse response) throws IOException {
        String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
        ServletOutputStream out = response.getOutputStream();
        Result errorInfoDTO =  new Result();
        try {
            Map<String, String> infoFromToken = JwtUtils.getInfoFromToken(token);
            String userRole = infoFromToken.get("role");
            String userName = infoFromToken.get("username");
            //  根据token 获取 用户信息  封装到UsernamePasswordAuthenticationToken对象中
            if (userName != null) {
                String[] roles1 = userRole.split("-");
                List<String> list = Arrays.asList(roles1);
                List<GrantedAuthority> listRoles = new ArrayList<GrantedAuthority>();
                for (String s : list) {
                    listRoles.add(new SimpleGrantedAuthority(s));
                }
                return new UsernamePasswordAuthenticationToken(infoFromToken, null, listRoles);
            }
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            errorInfoDTO.setCode(500);
            errorInfoDTO.setMessage("认证解析失败");
            String s = JSONObject.toJSONString(errorInfoDTO);
            out.write(s.getBytes());
            out.flush();
            out.close();
        }
        return  null;
    }
}
  public static Map<String,String> getInfoFromToken(String token) throws Exception {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        Map<String,String> map=new HashMap<>();
        map.put("id",claims.get("id").toString());
        map.put("username",claims.get("username").toString());
        map.put("role",claims.get("role").toString());
        return map;
    }

 鉴权就是登录成功之后,前端访问别的请求时候,就会自动去鉴权,这时候每次访问就会头部带有一个token的,所以每次请求,这边就会去看头部是不是有个token,要是没有携带token或者token开头没有带最开始装配的前缀就放行直接出个异常。

如果正确携带了token,就开始往下解析token,正常解析之后将返回的UsernamePasswordAuthenticationToken放入Spring security的上下文之中。要是权限不足,内部就会报一个权限不够的异常,这里我只能在全局捕捉那边将其显示出来。

   @GetMapping("/findMenuWare")
    @PreAuthorize("hasAuthority('teacher')")
    public Result<Object> findMenuWare(){
        Map<String,String> userInfo = (Map<String, String>) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Integer userInfoId=  Integer.parseInt(userInfo.get("id"));
        List<Tree<String>> findMenuWare = menuService.findmenuware(userInfoId);
        return new Result<>(StatusCode.SUCCESS,"查询成功", findMenuWare);
    }

 在鉴权的时候用用户信息map放入Spring security上下文里面,所以这边可以直接拿出来使用用户id。

这些是用户名密码登录。短信的话,就是在前端请求验证码的时候,将验证码放入session之中。

package com.ccas.common.jwt;

public class JWTSAMAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;



    public JWTSAMAuthenticationFilter(AuthenticationManager authenticationManager) {
        setAuthenticationManager(authenticationManager);
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/teach/common/smslogin");

        System.out.println("filter==="+authenticationManager+"---");
    }


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

        // 从输入流中获取到登录的信息
        try {
            JSONObject jsonObject = new JSONObject();  //   前端 发送 json 对象  username  password
            JWTUser loginUser  = jsonObject.parseObject(request.getInputStream(), JWTUser.class);
            String code = (String) request.getSession().getAttribute("code");
            if (code.equals(loginUser.getCode())){
                loginUser.setPassword("www.caoji.com");
                loginUser.setUsername(loginUser.getPhone());
                return authenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
                );
            }else {
                Result info = new Result();
                info.setCode(500);
                info.setMessage("短信验证码错误");
                response.setContentType("text/json;charset=utf-8");
                String s = JSONObject.toJSONString(info);
                response.getWriter().print(s);
                return null;
            }

        } catch (Exception e) {
            try {
                Result info = new Result();
                info.setCode(500);
                info.setMessage("账号或密码认证失败");
                response.setContentType("text/json;charset=utf-8");
                String s = JSONObject.toJSONString(info);
                response.getWriter().print(s);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    public void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        //  认证成功 我们需要将用户信息 生成token   发送给客户端
        UserInfo userInfo = (UserInfo) authResult.getPrincipal();
        Collection<GrantedAuthority> authorities = userInfo.getAuthorities();
        GrantedAuthority[] objects = authorities.toArray(new GrantedAuthority[]{});
        StringBuffer sb = new StringBuffer();
        for (int i=0;i<authorities.size();i++){
            String authority = objects[i].getAuthority();
            if(i==authorities.size()-1){
                sb.append(authority) ;
            }else{
                sb.append(authority+"-") ;
            }
        }
        userInfo.setRole(sb.toString());

        String token = null;
        response.setContentType("application/json; charset=utf-8");

        Result<Object> errorInfoDTO = new Result();
        try {
            token = JwtUtils.generateToken(userInfo);
            String tokenStr =  JwtUtils.TOKEN_PREFIX+ token;
            Map<String,Object> map=new HashMap<>();
            map.put("token",tokenStr);
            map.put("role",sb.toString());
            response.setHeader(JwtUtils.TOKEN_NAME,tokenStr); //  header形式 发送给客户端浏览器
            errorInfoDTO.setCode(200);
            errorInfoDTO.setMessage("登录成功");
            errorInfoDTO.setData(map);
        } catch (Exception e) {
            e.printStackTrace();
            errorInfoDTO.setCode(500);
            errorInfoDTO.setMessage("token生成失败");
        }
        String s = JSONObject.toJSONString(errorInfoDTO);
        response.getWriter().print(s);
    }

    @Override
    public  void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setStatus(403);
        response.setContentType("application/json; charset=utf-8");
        if (failed instanceof BadCredentialsException) {
            response.getWriter().write("authentication failed, reason: 密码错误"  );
        }else{
            response.getWriter().write("authentication failed, reason: " + failed.getMessage());
        }
    }
}

  @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                    UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if ((!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) && !presentedPassword.equals("www.caoji.com")) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "密码错误"));
        }
    }

jWTUser就是把上面用户名密码和这边的手机号验证码封装了一个json对象了,想要好接受来着。改了一个密码校验的源码,要是正确密码或者是我自定义的"www.caoji.com"都算验证通过。前端传来的code和之前存在session之中的code进行校验,要是一致,手动把密码设置为我的caoji自定义密码,反正这样也是可以走得通。

就这样短信验证也是可以了。

做这个的时候,但是Springsecurity好变态,各种重写简直了。但是我还是想说pro真的好香。那个显示栏,我爱了。争取走上苹果六件套!嘻嘻

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值