加入依赖,加个配置即可——应用场景:eureka server,hystrix dashboard,springboot admin等,用于增加登录验证(基于COOKIE:jssessionId实现用户session的,不能用postman等工具来测试)。该应用场景效果基本nginx配置账号密码验证差不多
spring:security:user:name: jwolfpassword: 123456
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;@EnableWebSecurity
public class MemoryUserSecurityConfig extends WebSecurityConfigurerAdapter {//用户认证@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//内存里面初始化几个用户auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder())//添加用户,密码,角色.withUser("zs").password("123456").roles("AAA")//链式编程.and().withUser("ls").password("123456").roles("BBB").and().withUser("ww").password("123456").roles("CCC", "primary").authorities("PPP");//PPP权限}//用户授权 ant风格的path@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制.antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问.antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC").antMatchers("/test-user/findAllUsers").permitAll().anyRequest().authenticated() //其它path都有登录才能访问.and().formLogin();//指定支持基于表单的身份验证,会暴露出/login /logout等端点,默认登录成功调向登录前的,可以自定义登录页面登录成功和失败重定向的url,例如.formLogin().loginPage("/myLogin").successForwardUrl("/xxxx").failureForwardUrl("/yyy")}private class MyPasswordEncoder implements org.springframework.security.crypto.password.PasswordEncoder {@Overridepublic String encode(CharSequence charSequence) {return charSequence.toString(); //用户输入密码处理}@Overridepublic boolean matches(CharSequence charSequence, String s) {return s.equals(charSequence.toString());//与security上下文存储的用户密码比对}}
}
@RestController
@RequestMapping("/test-user")
public class TestUserController {@RequestMapping("/addUser")String addUser() {return "这是添加用户!!!";}@RequestMapping("/deleteUser")String deleteUser() {return "这是删除用户!!!";}@RequestMapping("/updateUser")String updateUser() {return "这是修改用户!!!";}@RequestMapping("/findAllUsers")String findAllUsers() {return "这是查询用户!!!";}
}
配置类主要注入bean UserDetailsService
@Configuration
public class DBUserSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;//这里从数据库中读取数据到spring security上下文@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());}//用户授权 ant风格的path@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制.antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问.antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC").antMatchers("/test-user/findAllUsers").permitAll().anyRequest().authenticated() //其它path都有登录才能访问.and().formLogin();//指定支持基于表单的身份验证,会暴露出/login /logout等端点}@Beanpublic PasswordEncoder getPasswordEncoder(){return new PasswordEncoder() {@Overridepublic String encode(CharSequence password) {return password.toString();}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {String encodeStr = encode(rawPassword);return encodedPassword.equalsIgnoreCase(encodeStr);}};}
}
需要实现spring security的 UserDetailsService 接口,下面基本就是常规的业务类型代码了,这里注入的UserMapper
@Service
public class SecurityUserServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SecurityUser user = userMapper.selectByUsername(username);if (user == null) {throw new BadCredentialsException("用户名或密码错误");}return user;}
}
entity是这样的,需要实现spring secury的UserDetails
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Getter
@Setter
public class SecurityUser implements UserDetails {private static final long serialVersionUID=1L;private long id;private String password;private String username;private List
}
userMapper.xml是这样的
使用权限注解需要启动类开启,该注解有几个参数,默认都是关的@EnableGlobalMethodSecurity(prePostEnabled = true),@DenyAll @RolesAllowed({"USER", "ADMIN"}) @PermitAll 等开关需要通过jsr250Enabled = true来开启@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true)
核心配置类DBUserSecurityConfig的.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") 改为权限配置应该是接口使用注解@PreAuthorize("hasAnyAuthority('PPP')"),如果数据存的rolename为ROLE_XXX,或存入SimpleGrantedAuthority的rolename有ROLE_前缀,就应该使用@PreAuthorize("hasAnyRole('PPP')"),role与authority区别参考https://www.cnblogs.com/Rocky_/p/11799772.html
增加jwt依赖
原生的security认证信息通过COOKIE-session保存会话session,用户客户端或服务端重启都会导致重新登录,多数情况需要整合jwt,这里较step3多了两个filter,一个用于登录成功将jwt token写到浏览器,一个用于访问资源时解析jwt token放入到security 认证上下文,免密加密用的security亲生的BCryptPasswordEncoder
import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.ibatis.javassist.bytecode.ByteArray;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import sun.security.provider.MD5;import java.util.ArrayList;
import java.util.Collection;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll() //应用首页所以用户都可以访问,完全无限制.antMatchers("/test-user/addUs*").hasRole("BBB") // 允许 AAA 角色访问//.antMatchers("/test-user/deleteUser/**").hasAnyAuthority("PPP") //允许有"PPP"权限的访问.antMatchers("/test-user/updateUser").hasAnyRole("AAA", "BBB", "CCC").antMatchers("/test-user/findAllUsers").permitAll().anyRequest().authenticated() //其它path都有登录才能访问.and().csrf().disable().formLogin()//指定支持基于表单的身份验证,会暴露出/login /logout等端点.and().addFilter(new JWTLoginFilter(authenticationManager())).addFilter(new JWTAuthenticationFilter(authenticationManager()));}@Beanpublic PasswordEncoder getPasswordEncoder() {return new PasswordEncoder() {@Overridepublic String encode(CharSequence password) {//用户注册调用该方法进行密码加密// BCryptPasswordEncoder对相同字符串每次加密都结果都不一样,但matches()时都能比较成功,具有更高安全性return new BCryptPasswordEncoder().encode(password);}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {//用户登录将用户表单传入的rawPassword与数据库的密码比对。return new BCryptPasswordEncoder().matches(rawPassword,encodedPassword);}};}}
两个过滤器
import com.alibaba.fastjson.JSON;
import com.construn.vehicle.common.base.entity.ResultEntity;
import com.construn.vehicle.user.entity.SecurityUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.stream.Collectors;/*** 验证用户名密码正确后,生成一个token,并将token返回给客户端* 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,* attemptAuthentication:接收并解析用户凭证。* successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。*/
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {private AuthenticationManager authenticationManager;public JWTLoginFilter(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {String username = request.getParameter("username");String password = request.getParameter("password");return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, null));}@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {Claims claims = Jwts.claims();SecurityUser user = (SecurityUser) auth.getPrincipal();claims.put("userId", user.getId());claims.put("role", auth.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));String token = Jwts.builder().setClaims(claims).setSubject(auth.getName()).setExpiration(new Date(System.currentTimeMillis() + 600 * 1000)).signWith(SignatureAlgorithm.HS512, "jwolf").compact();response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");ResultEntity result = ResultEntity.success(token, "登录成功");PrintWriter out;try {out = response.getWriter();out.print(JSON.toJSONString(result));out.flush();out.close();} catch (IOException e) {e.printStackTrace();}}
}
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;public class JWTAuthenticationFilter extends BasicAuthenticationFilter {public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}/*** 从request中获取token并解析,拿到用户信息,放置到SecurityContextHolder,这样便完成了springsecurity和jwt的整合。*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {String token = request.getHeader("Authorization");if (token == null || !token.startsWith("Bearer ")) {chain.doFilter(request, response);return;}Claims claims = Jwts.parser().setSigningKey("jwolf").parseClaimsJws(token.replace("Bearer ", "")).getBody();List
其中登录过滤器可以优化,抽出JWT工具类,并可以不用该过滤器,使用登录成功处理器AuthenticationSuccessHandler进行替换,登录失败及登陆security也提供了类似的接口可以自己实现然后在security核心配置即可如登录成功: .successHandler(userLoginSuccessHandler) 参考https://www.tuicool.com/articles/Q7fU73E