SpringSecurity

Java / 2020-05-05

用户类和角色类

@Entity
@Table(name = "user")
@Data
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    private List<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<? extends GrantedAuthority> authorities;

        authorities = roles.stream().filter(it -> it.getName() != null)
                .map(it -> new SimpleGrantedAuthority(it.getName()))
                .collect(Collectors.toList());

        return authorities;
    }

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

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

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

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

用户类需要实现userdetails用户详情类,主要实现一个方法把用户的所有的角色名构成简单用户认证放到用户的认证方式中。

@Entity
@Table(name = "role")
@Data
@ToString
@EqualsAndHashCode
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "name")
    private String name;

    public Role(){

    }

    public Role(String name){
        this.name = name;
    }
}

角色类只有角色名,与用户是多对多的关系,一个用户有多个角色,一个角色可以被多个用户拥有

操作用户的DAO

public interface UserRepository extends JpaRepository<User,Integer> {

    User findByUsername(String username);
}

yml配置

jwt:
  secret: redarm
  expired: 86400

配置jwt的秘钥以及token过期时间为86400秒

jwt工具类

@Component
public class JwtTokenUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expired}")
    private long expired;

    private static final String USER_NAME = "sub";

    private static final String CREATE_TIME = "created";

    public static final String TOKEN_HEADER = "auth";

    public static final String TOKEN_PREFIX = "Bearer";

    public String createToken(User user){

        String token = null;

        Map<String , Object> claims = new HashMap<>();

        claims.put(USER_NAME,user.getUsername());
        claims.put(CREATE_TIME,new Date());

        try {
            token = Jwts.builder()
                    .setClaims(claims)
                    .setExpiration(new Date(System.currentTimeMillis() + expired * 1000))
                    .signWith(SignatureAlgorithm.HS256,secret)
                    .compact();
        } catch (Exception e){
            e.printStackTrace();
        }

        return token;
    }

    public Claims getClaimsFromToken(String token){
        Claims claims = null;

        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e){
            e.printStackTrace();
        }

        return claims;
    }

    public boolean tokenExpired(String token){
        return getClaimsFromToken(token).getExpiration().before(new Date());
    }

    public String getUsernameFromToken(String token){
        String username = null;

        try {
            username = getClaimsFromToken(token).getSubject();
        } catch (Exception e){
            e.printStackTrace();
        }

        return username;
    }
}

配置的秘钥和过期时间读进来
生产token:用户名,创建时间和过期时间组成token的第二部分,第三部分为前两部分加密生成的
auth:token在header中的关键字
Bearer:加到所有token的前6个字母

过滤器

拦截所有请求,如果header中有token,从token中取出用户详情,把用户详情包括用户权限加到当前请求的SpringSecurity认证中供后面使用,
如果请求中没有token,那么当前请求的认证权限就为空,这个请求无法使用需要权限才能使用的方法

public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserRepository userRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String header = httpServletRequest.getHeader(JwtTokenUtil.TOKEN_HEADER);

        if (header != null && header.startsWith(JwtTokenUtil.TOKEN_PREFIX)){
            String token = header.substring(JwtTokenUtil.TOKEN_PREFIX.length());

            String username = jwtTokenUtil.getUsernameFromToken(token);

            User user = userRepository.findByUsername(username);

            if (user != null && SecurityContextHolder.getContext().getAuthentication() == null){
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());

                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));

                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }

        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

配置SpringSecurity

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

    @Autowired
    private UserRepository userRepository;

    @Bean
    public UserDetailsService userDetailsService(){
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

                User user = null;

                user = userRepository.findByUsername(s);

                if (user == null){
                    throw new UsernameNotFoundException("username not found");
                }

                return user;
            }
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())

                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public JwtTokenFilter jwtTokenFilter(){
        return new JwtTokenFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()

                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()

                .authorizeRequests()

                .antMatchers(HttpMethod.OPTIONS).permitAll()

                .antMatchers("/**").permitAll()

                .anyRequest().authenticated();

        http
                .addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        http
                .headers().cacheControl();
    }
}

实现自定义用户认证服务(userDetailsService)并配置到SpringSecurity中,配置SpringSecurity:关闭csrf,关闭session,配置哪些请求需要什么权限,添加过滤器,关闭header缓存

登录注册请求服务

public interface UserService {

    String login(LoginDTO loginDTO);

    User register(User user);
}

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public String login(LoginDTO loginDTO) {
        String token = null;

        User user = userRepository.findByUsername(loginDTO.getUsername());

        System.out.println(user.getPassword());

        if (!passwordEncoder.matches(loginDTO.getPassword(),user.getPassword())){
            return "login false";
        }

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

        token = jwtTokenUtil.createToken(user);

        return token;
    }

    @Override
    public User register(User user) {

        try {
            if (userRepository.findByUsername(user.getUsername()) != null){
                return null;
            }

            user.setPassword(passwordEncoder.encode(user.getPassword()));
        } catch (Exception e){
            e.printStackTrace();
        }

        return userRepository.save(user);
    }
}

DTO:

@Data
@ToString
public class LoginDTO {

    @NotBlank(message = "username can not null")
    private String username;

    @NotBlank(message = "password can not null")
    private String password;
}

登录注册路由

@RestController
@RequestMapping(value = "api")
public class UserCongroller {

    @Autowired
    private UserService userService;

    @PostMapping(value = "login")
    public String login(@RequestBody @Valid LoginDTO loginDTO){
        return userService.login(loginDTO);
    }

    @PostMapping(value = "register")
    public User register(@RequestBody User user){
        return userService.register(user);
    }
}

验证权限路由

@RestController
@RequestMapping(value = "api")
public class TestController {

    @GetMapping(value = "test1")
    @PreAuthorize("hasAuthority('ADMIN')")
    public String test1(){
        return "test1 success";
    }
}