SpringSecurity做JWT认证授权详解

网友投稿 273 2022-09-13

SpringSecurity做JWT认证授权详解

第一、Spring Security简介

第二、JWT简介

第三、使用方法

使用JWT,我们只需要在请求的请求头上添加如图下类似的数据(token)。后端根据需要认证的url进行拦截,取出Hearders里面的数据,紧接着解析出这段token的包含的信息,判断信息是否正确即可。token其实就是根据信息加密而来的一段字符串,我们将需要用到的信息放到token中,token包含的信息尽可能的简洁。

第四、 设计流程

1.编写通过用户id或用户手机号码查询User和Role的方法2.编写Token生成工具类3.继承UserDetails接口4.继承UserDetailsService接口,实现用户认证方法5.编写用户账号验证失败处理器与权限不足处理器6.编写Token验证过滤器7.配置SpringSecurity Config8.实现登录方法

第五、实现过程---引入pom依赖

org.springframework.boot spring-boot-starter-security

第六、 实现过程---实现用户登录方法实

用户通过手机号及密码进行登录,我们需要先获取用户的身份信息以及角色信息

UserMapper.xml

UserMapper.java

User selUserAndRoleByPhone(String phone); User selUserAndRoleById(String id);

UserDaoImpl.java

public User selUserAndRoleByPhone(String phone) { User user = userMapper.selUserAndRoleByPhone(phone); return user; } public User selUserAndRoleById(String id){ User user = userMapper.selUserAndRoleById(id); return user; }

UserService.java

User getUserAndRoleByPhone(String phone); User getUserAndRoleById(String id);

UserServiceImpl.java

public User getUserAndRoleByPhone(String phone) { User user = userDao.selUserAndRoleByPhone(phone); return user; } public User getUserAndRoleById(String id) { User user = userDao.selUserAndRoleById(id); return user; }

操作数据库获取用户身份信息的代码就到此为止了,接下来就开始编写SpringSecurity+jwt的认证代码了

编写Token生成工具类----JwtTokenUtil

工具类主要用作生成token、刷新token以及验证token。Token和Session一个很大的区别就是无登录状态,我们可以利用清除session做登出的操作,但无法利用token直接做登出操作,后续会进行讲解。 这个token里的信息比较简单,只存放了sub和create,你可以根据自己业务需求在generateToken(UserDetails userDetails)方法里面添加不同的数据即可,后续通过getClaimsFromToken方法获取Claims对象,接着调用Claims对象的get方法获取出对应的数据即可。

继承UserDetails接口

UserDetails接口是SpringSecurity框架用于认证授权的一个载体,只有实现了这个接口的类才能被SpringSecurity验证,

public class User implements UserDetails { private String id; private String name; private String password; private String phone; private List roles; public List getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; } public User(String id, String name, String password, String phone) { this.id = id; this.name = name; this.password = password; this.phone = phone; } public User() { super(); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } //获取用户角色权限,此处从数据库表Role中获取 @Override public Collection getAuthorities() { List auths = new ArrayList<>(); List roles = getRoles(); if (roles!=null) { for (Role role : roles) { auths.add(new SimpleGrantedAuthority(role.getRoleName())); } } return auths; } //这个是UserDetails默认实现获取密码的方法 @Override public String getPassword() { return password; } //这里getUsername翻译过来就是获取用户名的意思,但这个可以作为我们获取用户信息的一个标识 @Override public String getUsername() { return id; } //用户账号是否过期,暂时没这个功能,默认返回true,即未过期 @Override public boolean isAccountNonExpired() { return true; } //用户账号是否锁定 @Override public boolean isAccountNonLocked() { return true; } //用户凭证是否过期 @Override public boolean isCredentialsNonExpired() { return true; } //账号是否可用 @Override public boolean isEnabled() { return true; } public void setPassword(String password) { this.password = password; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; }}

编写登录认证方法JwtUserDetailsServiceImpl.java

该类位于com.viu.technology.service.auth包下(自行建包) JwtUserDetailsServiceImpl实现了UserDetailsService接口,SpringSecurity会去IOC容器中寻找实现这个接口的实现类,并将该实现类作为默认的认证类。这个类主要用于获取用户身份信息,并不需要我们去判断用户名和密码是否匹配。参照UserDetails实现的getPassword和getUsername方法。

这里之所要对username的长度进行判断是因为,我们登录的时候用的是手机号+明文密码进行登录,而保存在token里的信息只有id。登录方法和Token认证过滤器都会调用loadUserByUsername方法,所以需要做一个判断。可能会有一点疑问,既然是这样,为什么不直接用手机号做为token的传递信息就好了呢,主要还是因为我们使用手机号查询的情况比较少,而表的主键id才是经常用的。

@Servicepublic class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired @Lazy private UserService userService; public JwtUserDetailsServiceImpl(){ } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = null; if (username.length() == 32) { user= userService.getUserAndRoleById(username); } else if(username.length()==11) { user= userService.getUserAndRoleByPhone(username); } log.info("user:" + user); if (user == null) { throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); }else{ return user; } }}

编写账号密码验证失败处理器EntryPointUnauthorizedHandler.java

@Componentpublic class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setStatus(401); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(JsonUtil.objectToString(Result.fail(ResultCode.USER_LOGIN_FIAL))); }}

编写账户权限不足处理器RestAccessDeniedHandler.java

@Componentpublic class RestAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setStatus(403); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(JsonUtil.objectToString(Result.fail(ResultCode.USER_PERMISSION_DENIED))); }}

编写Token验证过滤器JwtAuthenticationTokenFilter.java

@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); //该字符串作为Authorization请求头的值的前缀 String tokenHead = "tech-"; if (authHeader != null && authHeader.startsWith(tokenHead)) { String authToken = authHeader.substring(tokenHead.length()); //从token中获取userId String userId = JwtTokenUtil.getUsernameFromToken(authToken); if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { //调用UserDetailsService的认证方法(JwtUserDetailsServiceImpl实现类) UserDetails userDetails = this.userDetailsService.loadUserByUsername(userId); //验证token是否正确 if (JwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); //将获取到的用户身份信息放到SecurityContextHolder中,这个类是为了在线程中保存当前用户的身份信息 SecurityContextHolder.getContext().setAuthentication(authentication); } } } else { log.info("没有获取到token"); } chain.doFilter(request, response); }}

配置SpringSecurity

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)//开启security方法级别权限控制注解public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler; @Autowired private RestAccessDeniedHandler restAccessDeniedHandler; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SimpleUrlAuthenticationSuccessHandler successHandler; @Autowired private SimpleUrlAuthenticationFailureHandler failureHandler; @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Override protected void configure(HttpSecurity throws Exception { .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() //这里的参数为不需要认证的uri,**代表匹配多级路径,*代表匹配一级路径,#代表一个字符.... .antMatchers( "/demo/**", "/user/generate/token" ).permitAll() //这里表示该路径需要管理员角色 .antMatchers("/auth/test").hasAnyAuthority("管理员") .anyRequest().authenticated() .and() .headers().cacheControl(); //添加认证过滤 UsernamePasswordAuthenticationFilter.class); //添加权限不足及验证失败处理器 } //这个为SpringSecurity的加密类 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}

实现登录方法

String login(String phone, String password);

UserServiceImpl.java

注意一下,UsernamePasswordAuthenticationToken会自动将password进行加密之后再比对,而我们之前写的注册用户方法是以明文方式存入数据库的,并没有加密,所以我们需要修改一下用户注册方法,然后重新注册.

public String login(String phone, String password) { //将用户名和密码生成Token UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(phone, password); //调用该方法时SpringSecurity会去调用JwtUserDetailsServiceImpl 进行验证 Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return JwtTokenUtil.generateToken(userDetails); } @Autowired PasswordEncoder passwordEncoder; public User registerUser(User user) { //在插入数据库时将原密码进行加密 user.setPassword(passwordEncoder.encode(user.getPassword())); User userRes = userDao.insertUser(user); Role roleRes = roleDao.insertRole(new Role("游客", user.getId())); List list = new ArrayList(); list.add(roleRes); if (null != userRes && null != roleRes) { userRes.setRoles(list); return user; } return null; }

UserController.java

@PostMapping(value = "/generate/token") public Result getToken(String phone, String password) throws AuthenticationException { String token = userService.login(phone, password); return Result.success(token); }

测试获取token接口

然后调用一下之前写的注册接口,发现没发注册,因为我们在SpringSecurity的配置中并没有开放这个接口的认证,自行添加。注册是不需要用户身份验证的,否则你让人家怎么注册?

测试Token是否能正常使用

UserController.java

@GetMapping("/self/info") public Result getUserSelfInfo() { //由于通过验证后我们会把用户对象存到SecurityContextHolder中,所以这时候我们能通过下面这句代码获取到用户的身份信息 User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return Result.success(user); }

接下来测试一下,如果能够正常获取就代表成功,记住token前面要加tech-这个几个字符串,看不顺眼的话自己去改过滤器

持续学习!!!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux 重要命令
下一篇:34亿销售投入换来27亿年亏损!“完美日记”营销向左研发向右!
相关文章

 发表评论

暂时没有评论,来抢沙发吧~