springsecurity+jwt
整体流程:
- 搭建springboot工程
- 导入springSecurity跟jwt的依赖
- 用户的实体类
- dao层
- service层(真正开发时再写,这里就直接调用dao层操作数据库)
- 实现UserDetailsService接口
- 实现UserDetails接口
- 验证用户登录信息的拦截器
- 验证用户权限的拦截器
- springSecurity配置
- 认证的Controller以及测试的controller
- 测试
1.导入依赖
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2.配置文件(application.properties)
#mysql setting
spring.datasource.url=jdbc:mysql://localhost:3306/ssj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#jpa setting
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#jpa自动建表
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.format_sql=true
spring.jpa.use_sql_comments=true
server.port=8102
3.用户实体类
@Entity
@Table(name = "jd_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "role")
private String role;
//getter set...
jwt依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
JwtTokenUtils
jwt工具类,对jjwt封装一下方便调用
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "jwtsecretdemo";
private static final String ISS = "echisan";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 604800L;
// 创建token
public static String createToken(String username, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
// 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 是否已过期
public static boolean isExpiration(String token){
return getTokenBody(token).getExpiration().before(new Date());
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
4.userRespsitory
public interface UserRepository extends CrudRepository<User, Integer> {
User findByUsername(String username);
}
5.实现UserDetailsService接口
根据用户名查找用户返回实现userdetails的对象。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findByUsername(s);
return new JwtUser(user);
}
}
6.实现UserDetails
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写一个能直接使用user创建jwtUser的构造器
public JwtUser(User user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
}
// 获取权限信息,目前博主只会拿来存角色。。
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 账号是否未过期,默认是false,记得要改一下
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账号是否未锁定,默认是false,记得也要改一下
@Override
public boolean isAccountNonLocked() {
return true;
}
// 账号凭证是否未过期,默认是false,记得还要改一下
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 这个有点抽象不会翻译,默认也是false,记得改一下
@Override
public boolean isEnabled() {
return true;
}
// 我自己重写打印下信息看的
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}
7.用户登录信息拦截验证
/**
* @Description: 拦截器用于获取用户登录的信息
* 只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,
* 不用自己查数据库再对比密码了,这一步交给spring去操作
* @Auther: shanpeng.wang
* @Create: 2020/12/28 16:43
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");//设置自定义登录路径
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象
// 所以就是JwtUser啦
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的格式应该是 `Bearer token`
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
}
// 这是验证失败时候调用的方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
8.用户角色权限验证
/**
* @Description: 权限校验
* @Auther: shanpeng.wang
* @Create: 2020/12/28 16:45
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.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) {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
return null;
}
}
9.编写config类
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
// 因为UserDetailsService的实现类实在太多啦,这里设置一下我们要注入的实现类
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// 测试用资源,需要验证了的用户才能访问
.antMatchers("/tasks/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/tasks/**").hasRole("ADMIN")
// 其他都放行了
.anyRequest().permitAll()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
10.测试
注册
url:localhost:8102/auth/login
参数:{
"username":"zhangshan","password":"123456","role":"ROLE_USER"
}
未登录下访问和未授权访问
登录
登录后会生成一个token
访问其他接口
携带token访问成功。