本文代码示例已放入github:请点击我
快速导航------>src.main.java.yq.SpringSecurity
SpringSecurity是什么?
答:Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架
为什么使用SpringSecurity?
答:它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。而且对权限控制比较灵活。说白了,就是可以与Spring无缝对接且灵活,功能强大。
SpringSecurity缺点?
答:缺点也很明显,首先基于Spring,也就是离开了Spring就,咳咳,这个倒是不影响,目前大多数开发还是跟Spring离不开的,而且相对来说学习成本比较大,新手入门比较难。
SpringSecurity与ApacheShiro谁好?
答:没有谁好谁差,都是主流的权限管理框架,SpringSecurity比ApacheShiro权限控制更灵活,ApacheShiro比SpringSecurity使用入门更简单,都可以达到企业级的web权限控制,谁用牛了都厉害,所以不存在说谁厉害,具体还是看业务场景进行技术选型。但是作为程序猿,还是可以都掌握的。
怎么使用SpringSecurity?
首先我去扣了一个比较简单的SpringSecurity执行的流程图:
开始之前我先说一下我这个Dome的执行流程,也就是简单的基于Token登录的模式
也就是用户登录之后会返回一个loginToken,这个loginToken在后续的请求都会带上它,才允许访问哪些接口,如果没有这个token,就会提示没有权限访问。
那么接下来就是用SpringBoot正式开始整合SpringSecurity
1.首先加入POM文件(部分核心依赖)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<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>
</dependencies>
2.创建SpringSecurity核心配置类
//lombok 注解 简化log日志的
@Slf4j
//不使用默认的SpringSecurity 使用我们自定义的
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class AuthorizationConfiguration extends WebSecurityConfigurerAdapter {
private static final String key = "key";
//进行权限认证的filter过滤器
@Autowired
private AuthFilter authFilter;
//数据源
@Autowired
private DataSource datasource;
//
@Autowired
private BaseApiService baseApiService;
@Autowired
private DIYAuthenticationEntryPoint diyAuthenticationEntryPoint;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//密码加密
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//用户身份验证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
//自定义的验证
userDetailsService(userDetailsServiceImpl())
.and()
.jdbcAuthentication()
//密码加密方式
.passwordEncoder(bCryptPasswordEncoder())
.dataSource(datasource)
.and()
.authenticationProvider(new RememberMeAuthenticationProvider(key));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//跨域支持
.cors()
.and()
//这个一定要关闭 不然的话使用post的请求会把报错 好像不关闭只能使用get请求
.csrf().disable()
//配置认证失败处理
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler())
.and()
//session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//开始授权的地方
.authorizeRequests()
//这些url可以不需要认证 直接访问
.antMatchers("/static/**","/userLogin","/error","/saveTest").permitAll()
//这个接口需要user权限
.antMatchers("/api/testTwo").hasAuthority("user")
//这个接口需要 admin权限
.antMatchers("/api/**").hasAuthority("admin")
// .anyRequest().hasAuthority("admin")
.and()
//在验证之前执行 也就是使用我们自己定义的filter过滤器进行用户验证
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
//禁止页面缓存
.headers().frameOptions().sameOrigin().cacheControl();
}
//没有授权的人(验证不通过
public AuthenticationEntryPoint unauthorizedHandler() {
// lambda
return (request, response, authException) -> {
log.error("auth error query path {}", request.getRequestURL());
response.setContentType("application/json;charset=utf-8");
//封装一个map返回页面
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("data","null");
objectObjectHashMap.put("message","您没有访问权限");
objectObjectHashMap.put("rtnCode","403");
response.getWriter().append(JSON.toJSONString(objectObjectHashMap));
};
}
//跨域
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("*"));
//允许访问的请求类型
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
//是否支持用户凭证
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
//允许header中携带的内容 loginToken:这是我们登录的token的名称
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type", "loginToken"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//所有请求都使用 CorsConfiguration
source.registerCorsConfiguration("/**", configuration);
return source;
}
//注入到容器 为什么需要在这里手动的注入 不适用注解 @Component等注入呢
//因为在 UserDetailsServiceImpl 中有其他的依赖项 如果在使用 @Component 注入
//那么SpringSecurity 还没有加载完成就会报错
@Bean
public UserDetailsServiceImpl userDetailsServiceImpl(){
return new UserDetailsServiceImpl();
}
//同上原理
@Bean
public LoginTokenService loginTokenService() {
return new LoginTokenService("key",userDetailsServiceImpl());
}
}
3.开始创建配置类里面的依赖类等(认证filter过滤器)
//只执行一次 用于在用户登录验证之前进行验证
@Component
public class AuthFilter extends OncePerRequestFilter {
//我们验证的授权token的key
private final static String AUTHORIZATIONHEAD = "loginToken";
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private LoginTokenService loginTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//如果当前的用户没有身份令牌 表示没有登录
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//调用授权方法
Authentication rememberMeAuth = authentication(request, response);
if (rememberMeAuth != null) {
// 如果授权成功
try {
//进行授权验证
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
// 保存值
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
} catch (AuthenticationException authenticationException) {
logger.error("认证失败 ex{} ", authenticationException);
}
}
}
//开始下一个过滤器
filterChain.doFilter(request, response);
}
//授权的方法
public Authentication authentication(HttpServletRequest request,HttpServletResponse response){
//得到请求中的 token
String loginToken = request.getHeader(AUTHORIZATIONHEAD);
if(StringUtils.isEmpty(loginToken)){
return null;
}
//验证token
return loginTokenService.check(loginToken,request,response);
}
}
4.创建一个基于Json Web Token(jwt)的token类
//token服务 用于验证token和创建token
public class LoginTokenService extends TokenBasedRememberMeServices {
public LoginTokenService(String key, UserDetailsService userDetailsService) {
super(key, userDetailsService);
}
//过期时间 20周
private final Long EXP_TIME = 1000L * TWO_WEEKS_S * 10;
//token验证是否合法
public Authentication check(String token, HttpServletRequest request, HttpServletResponse response) {
try {
//获得令牌
String[] tokens = decodeCookie(token);
//进行验证token 并且获得对象
UserDetails userDetails = processAutoLoginCookie(tokens, request, response);
return createSuccessfulAuthentication(request, userDetails);
} catch (Exception e) {
throw new IllegalArgumentException("错误的loginToken");
}
}
//创建用户token
public String creaetToken(String userName,String passWord){
//过期时间
Long exp = System.currentTimeMillis()+EXP_TIME;
String tokenSignature = makeTokenSignature(exp, userName, passWord);
return encodeCookie(new String[]{exp.toString(),userName,tokenSignature});
}
}
5.创建一个可以得到用户身份认证信息的类
//可以用于得到登录用户的信息 用于用户身份认证
public class SecurityUserDetails implements UserDetails {
//这是保存的主体信息
private Test test;
//所有的权限
private List<GrantedAuthority> authorities;
public SecurityUserDetails(Test test,List<GrantedAuthority> authorities){
this.test = test;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return test.getPassWord();
}
@Override
public String getUsername() {
return test.getId().toString();
}
public Test getTest(){
return test;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
6.创建一个用户认证的类,用于用户身份认证(登录)
//用户信息验证
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private MySqlService mySqlService;
@Autowired
private MySqlMapper mySqlMapper;
//认证和授权
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
long id = Long.parseLong(username);
List<GrantedAuthority> authorities = new ArrayList<>();
Test testById = mySqlService.getTestById(id);
if(testById == null){
throw new UsernameNotFoundException("用户不能为空");
}
//权限赋值
authorities.add(new SimpleGrantedAuthority("admin"));
//这里就是我们刚刚那个类
return new SecurityUserDetails(testById,authorities);
}
}
7.创建一个Controller进行使用SpringSecurity登录
@RestController
public class UserController extends BaseApiService {
//我的数据库访问层
@Autowired
private MySqlService mySqlService;
//用于生产token和验证token的
@Autowired
private LoginTokenService loginTokenService;
//SpringSecurity登录授权
@Autowired
private UserDetailsServiceImpl userDetailsService;
@PostMapping(value = "/userLogin")
public ResponseBase login(@RequestBody Test test){
Test testById = mySqlService.getTestById(test.getId());
if(testById == null){
return setResultError("用户账号错误");
}
if(! testById.getPassWord().equals(test.getPassWord())){
return setResultError("密码错误");
}
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
userDetailsService.loadUserByUsername(testById.getId().toString());
String loginToken = loginTokenService.creaetToken(testById.getId().toString(), testById.getPassWord());
objectObjectHashMap.put("loginToken",loginToken);
objectObjectHashMap.put("userName",testById.getUserName());
return setResultSuccessData(JSON.toJSONString(objectObjectHashMap),"登陆成功");
}
}
好了,到了这里基本上的Dome示例就完成了,如果还不会SpringSecurity的可以好好挖掘一下,另外我们这里的只是简单的单点登录功能,SpringSecurity所有的功能远不止如此,比如SpringSecurityOauth2等等
谢谢大家的观看
本文代码示例已放入github:请点击我
快速导航------>src.main.java.yq.SpringSecurity