电脑
intelliJ idea,jdk1.8,mysql
集成spring security首先需要引入依赖:
security主要做两件事情:第一件事是用户验证,第二件事是判断权限。那么如何做着两件事情呢?接着往下看,首先我们需要配置一个配置类,这个配置类主要是做HTTP 验证和自定义身份验证。配置类如下:package cn.cy.config; import cn.cy.core.filter.JWTAuthenticationFilter; import cn.cy.core.filter.JWTLoginFilter; import cn.cy.core.handler.JWTAuthenctiationSuccessHandler; import cn.cy.service.SysUserDedailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * @EnableWebSecurity 主要是引入WebSecurityConfiguration.class和SpringWebMvcImportSelector.class, * Spring Security默认是禁用注解的,要想开启注解, * 需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解, * 来判断用户对某个控制层的方法是否具有访问权限 * * @EnableGlobalMethodSecurity(securedEnabled=true) 开启@Secured 注解过滤权限 * @EnableGlobalMethodSecurity(jsr250Enabled=true)开启@RolesAllowed 注解过滤权限 * @EnableGlobalMethodSecurity(prePostEnabled=true) 使用表达式时间方法级别的安全性 * * @PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问 * @PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常 * @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果 * @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值 * * Created by 30721 on 2019/7/10. */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled =true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserDedailsService sysUserDedailsService; @Autowired private JWTAuthenctiationSuccessHandler jwtAuthenctiationSuccessHandler; /** * 可以根据现实预期,编写自定义加密方法 * @return */ @Bean public PasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } /** * 设置 HTTP 验证规则 * 默认配置为: * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * 退出登录的地址为 '/logout',退出成功后跳转到页面 '/login' * 默认启用 CSRF * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //开启跨域 关闭csrf。 http.cors().and().csrf().disable() .authorizeRequests() //开启swagger2的必要权限 .antMatchers('/v2/api-docs', '/configuration/ui', '/swagger-resources', '/configuration/security', '/swagger-ui.html', '/webjars/**','/swagger-resources/configuration/ui').permitAll() //开启api的匿名访问 .antMatchers('/api/**').permitAll() //角色为管理员的时候才有权限访问admin地址 .antMatchers('/admin/**').access('hasRole('ADMIN')') //角色为USER的时候才有权限访问user地址 .antMatchers('/sysUsers/**').access('hasRole('USER')') //由于该项目定位是前后端分离,这个项目只涉及到后端,所以没有HTML css等文件,所以注释掉,需要的就将他打开 //.antMatchers('/', '/*.html','/favicon.ico', '/css/**', '/js/**').permitAll() .anyRequest().authenticated() .and() .formLogin() // 自定义登入页 // .loginPage('/login') // 自定义登入路径 // .loginProcessingUrl('') // 认证成功后的跳转路径 // .successForwardUrl('/home') // 授权成功处理程序 --- 但是我试了一下却不能正常跳转到处理器的方法里面,暂时还没找到原因 // .successHandler(jwtAuthenctiationSuccessHandler) .permitAll() .and() //注销 .logout() .permitAll(); //拦截器重写 http.addFilter(new JWTLoginFilter(authenticationManager())); //授权验证拦截重写 http.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); } /** * 使用自定义身份验证组件 * 重写安全认证,加入密码加密方式 * @param auth * @throws Exception */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserDedailsService).passwordEncoder(bCryptPasswordEncoder()); } }
写完配置文件,接下来就是编写登录的拦截验证和授权了:package cn.cy.core.filter; import cn.cy.model.ResultBean; import cn.cy.util.JWTUtil; import cn.cy.util.ResponseUtil; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 验证用户名密码正确后,生成一个token,并将token返回给客户端 * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 * attemptAuthentication :接收并解析用户凭证。 * successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。 * Created by 30721 on 2019/7/10. */ public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** * 解析用户并判断其合法性。 * @param request * @param response * @return * @throws AuthenticationException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { // 自定义的页面--获取user信息 // SysUser user = GsonUtil.getInstance().fromJson(request.getReader(),SysUser.class); // return authenticationManager.authenticate( // new UsernamePasswordAuthenticationToken( // user.getName(), // user.getPsw(), // new ArrayList<>())); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getParameter('username'), request.getParameter('password'), new ArrayList<>())); } catch (JsonSyntaxException | JsonIOException | NullPointerException e) { e.printStackTrace(); ResultBean result = new ResultBean<>(); result.setState(1); result.setMsg('参数格式错误'); ResponseUtil.responseJson(response, HttpStatus.BAD_REQUEST.value(), result); } return null; } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException { UserDetails userDetails = (UserDetails) auth.getPrincipal(); Map
验证用户的接口(接口中没有写密码验证,是因为springboot帮助我们做了密码验证,但是需要在配置类中告诉springboot你用的是什么加密方式,官方推荐的是bCryptPasswordEncoder,我这边用的也是这个,这个加密 方式是可以自己定义的,这边不做描述)代码如下:/** * Created by 30721 on 2019/7/11. */ @Service public class SysUserDedailsService implements UserDetailsService { @Autowired private SysUserService sysUserService; /** * 查询用户是否存在,并授予角色 * 这边不做密码校验,密码校验交给springboot。 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = sysUserService.getSysUser(username); if (null == sysUser) { throw new UsernameNotFoundException(username); } List
由于这边用的是token方式记录用户信息与权限,服务器没有记录用户状态,所以服务器算是无状态服务器,无状态服务器的好处体现在分布式系统中这里也不多赘述。系统使用token方式而服务器没有记录用户的状态,所以服务器需要解析token来判断用户是否为登入状态和用户权限。所以需要编写授权拦截器解析token,代码如下:/** * Created by 30721 on 2019/7/11. */ public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader('Authorization'); if (header == null || !header.startsWith('Bearer ')) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(request); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader('Authorization'); try { if (token != null) { token = token.replace('Bearer ', ''); //解析token Claims claims = JWTUtil.verifyJwt(token); if (claims != null) { List
由于运用到token,所以需要编写一个生成和解析token的工具类,代码如下:package cn.cy.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.tomcat.util.codec.binary.Base64; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * Created by 30721 on 2019/7/12. */ public class JWTUtil { /** * token 过期时间, 单位: 秒. 这个值表示 30 天 */ private static final long TOKEN_EXPIRED_TIME = 30 * 24 * 60 * 60; /** * 加密解密密钥 */ private static final String JWT_SECRET = '5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556069d6631545f42aa6e3500f2e'; public static final String jwtId = 'tokenId'; /** * 创建JWT */ public static String createJWT(Map
由于项目开始的定位就是前后端分离,所以本次的验证,我借助了Fiddler 4的工具来进行的。
验证成功后我们试一下能不能访问home页面,因为我们使用的token的方式来进行的,所以我们需要将登入成功后的token拿到,如下图;拿到token后粘贴在请求参数的输出框中,修改路径,点击执行。