前言
使用SpringSecurity+jwt组合对用户的权限以及token进行管理,用户在成功登陆后,会分发一个token令牌,如果没有将某个接口进行权限设置,那么访问该页面时需要带着token令牌进行访问,后端将会对token令牌进行校验,如果校验通过则可以对该接口进行请求。
整体思路:
1.导入springSecurity跟jwt的maven依赖
2.用户登录的实体类(这里就不详细说明mapper和service)
3.实现UserDetailsService接口
4.JwtUser实现UserDetails接口
5.JwtTokenUtils工具类
6.验证用户登录信息的拦截器(JWTAuthenticationFilter)
7.验证用户权限的拦截器(JWTAuthorizationFilter)
8.springSecurity配置
9.认证的Controller
10.前端vue进行测试(跨域可以在博主的其他文章中查看)
1.导入springSecurity跟jwt的maven依赖
<!--springSecuriy-->
<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.1</version>
</dependency>
2.用户登录的实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="User对象", description="")
public class User {
private static final long serialVersionUID = 1L;
@TableId(value = "uid", type = IdType.AUTO)
private Long uid;
private String username;
private String password;
private String role;
}
3.实现UserDetailsService接口
这里使用的是mybatisplus对数据库进行访问,在博主其他文章中也有笔记
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
// 用户登录逻辑和验证处理
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//将用户传来的名字进行后缀判断角色
JwtUser jwtUser = null;
QueryWrapper<Student> wrapper = new QueryWrapper<>();
wrapper.eq("username",s);
User user = studentMapper.selectOne(wrapper);
return new JwtUser(user);
}
}
4.JwtUser实现UserDetails接口
public class JwtUser implements UserDetails {
private Long id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
public JwtUser(User user) {
id = user.getUid();
username = user.getUsername();
password = user.getPassword();
//这里说明一下,必须要加上ROLE_开头,或者在数据库直接以这个开头
authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_"+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;
}
@Override
public boolean isEnabled() {
return true;
}
}
5.JwtTokenUtils工具类
用于设置token过期时间,以及token的前缀等
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";
// 角色的key
private static final String ROLE_CLAIMS = "rol";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 创建token
public static String createToken(String username,String role) {
long expiration =EXPIRATION;
HashMap<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.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 String getUserRole(String token){
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
// 是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
6.验证用户登录信息的拦截器(JWTAuthenticationFilter)
//进行用户账号的验证
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
//设置登录请求的url
super.setFilterProcessesUrl("/login");
}
@SneakyThrows
@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 (BadCredentialsException b){
System.out.println("密码错误");
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("密码错误");
} catch (IOException e) {
e.printStackTrace();
}
return null;
//无此用户时抛出异常
}catch (InternalAuthenticationServiceException i){
System.out.println("没有此用户");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("xxx");
return null;
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
System.out.println("登录成功");
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
String token = JwtTokenUt)ils.createToken(jwtUser.getUsername(), role);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
System.out.println(token);
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
//这里我还将该用户的id进行返回了
response.setIntHeader("uid",jwtUser.getUid().intValue());
}
//配置失败返回的信息,当然成功的也可以返回思路一样,重写successful,这里我就不写了
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
System.out.println("用户登陆失败 AjaxAuthFailHandler");
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter out = response.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
out.flush();
out.close();
}
}
7.验证用户权限的拦截器(JWTAuthorizationFilter)
进行用户账号的验证(查看是否有token,token是否正确,token是否过时)
//进行用户账号的验证
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,则进行解析,并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (TokenIsExpiredException e) {
//返回json形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
response.getWriter().flush();
return;
}
super.doFilterInternal(request, response, chain);
}
// 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtils.isExpiration(token);
if (expiration) {
throw new TokenIsExpiredException("token超时了");
} else {
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
}
return null;
}
}
8.springSecurity的配置
@Configuration //配置类
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
@Autowired
UserServiceImpl userService;
//定义登陆成功返回信息
private class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("用户[" + SecurityContextHolder.getContext().getAuthentication().getPrincipal() +"]登陆成功!");
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
out.flush();
out.close();
}
}
//定义登陆失败返回信息
private class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("用户登陆失败 AjaxAuthFailHandler");
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter out = response.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
out.flush();
out.close();
}
}
//请求授权验
@Override
protected void configure(HttpSecurity http) throws Exception {
// .denyAll(); //拒绝访问
// .authenticated(); //需认证通过
// .permitAll(); //无条件允许访问
// 跨域一定要加上csrf();
// 访问权限
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers("/tologin","/islogin","/register","/registererr").permitAll()
//.antMatchers("/User").hasRole("USER")
// .antMatchers("/Admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
//不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//登录配置
http.formLogin()
.successHandler(new AjaxAuthSuccessHandler())
.failureHandler(new AjaxAuthFailHandler())
//固定名字,其实默认也是username跟password,这里也可以不写
.usernameParameter("username")
.passwordParameter("password")
//设置自己的登录界面(如果不设置,将会是自带的登录界面这里我们使用自定义的登录界面)
.loginPage("/tologin")
//表单提交的url
.loginProcessingUrl("/login");
}
// 用户授权验证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(passwordEncoder()
);
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
// 这里采用雪花加密方式对密码进行加密解密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
9.认证的Controller
//单跨域的使用
@CrossOrigin
@Controller
public class PageController {
@GetMapping("/tologin")
public String tologin(){
return "redirect:http://localhost:8001/Login";
}
}
10.前端vue进行测试
这里需要有一点vue的基础, Vue这里不会详细讲用法了,但是会讲我是如何保存token,携带token发送请求的,这里我发送请求用的是axios,关于vue跨域配置等后期可能会写一篇相关的文章。
Login.vue
只给到了发送请求的地方,这里我将得到的token以及uid都存放到session中
var data = JSON.stringify(that.loginForm);
console.log(that.loginForm.username)
this.$axios
.post(url, data)
.then(function (res) {
//登录成功后,将后端给的token以及uid都存在session中
window.sessionStorage.setItem("token", res.headers.token);
window.sessionStorage.setItem("uid",res.headers.uid);
that.$message.success("登陆成功");
}).catch((err) => {
//账号或密码有误时
var code = err.response.status;
return this.$message.error("登录失败!账号或密码错误");
});
main.js
这里从session中获取token,并放到axios的请求头中,后面使用axios的请求都会自动带上token
//vue 的main.js中配置
//给axios请求设置默认的token请求头
const token = window.sessionStorage.getItem("token");
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers['Authorization'] = token;
axios.defaults.headers.post['Content-Type'] = 'text/plain';
Vue.prototype.$axios = axios;
记录我的学习笔记