1、SpringSecurity
简介
spring security 的核心功能主要包括:- 认证 (你是谁) - 授权 (你能干什么) - 攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
比如,对于username password认证过滤器来说,
会检查是否是一个登录请求;
是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
如果不满足则放行给下一个。
下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
下面结合具体的项目进行介绍,以下是项目的结构图:
下面是resource文件夹的详细内容
2、Pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入springsecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--加入thymeleaf对springsecurity的支持-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
</dependencies>
在springboot中引入SpringSecurity的依赖只需要引入spring-boot-starter-security即可。
3、主要的html页面
1.登录页面login.html如下:
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>SpringSecurity</title>
</head>
<body>
<h2>登录页面</h2>
<form action="/admin/login" method="post">
用户名:<input type="text" name="user_name" value=""/><br/>
密码:<input type="text" name="pass_word" value=""/><br/>
记住我:<input type="checkbox" name="remember-me" value="true"> <br/>
<button type="submit">登录</button>
</form>
</body>
</html>
2.登录成功之后的主页面index.html如下:
<!doctype html>
<html xmlns:th="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号: <span sec:authentication="name">00</span> <br>
登录账号:<span sec:authentication="principal.username">11</span> <br>
凭证:<span sec:authentication="credentials">22</span> <br>
权限和角色:<span sec:authentication="authorities">33</span> <br>
客户端地址:<span sec:authentication="details.remoteAddress">44</span> <br>
sessionId:<span sec:authentication="details.sessionId">55</span> <br>
<hr/>
<br/>
通过权限判断:<br>
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br>
通过角色判断:<br>
<button sec:authorize="hasRole('syw')">新增</button>
<button sec:authorize="hasRole('syw')">删除</button>
<button sec:authorize="hasRole('lmy')">修改</button>
<button sec:authorize="hasRole('lmy')">查看</button>
<hr/>
<br>
首页 <a href="/admin/admin"> admin权限</a>
<a href="/admin/root"> root权限</a>
<a href="/admin/normal"> normal权限</a>
<br>
springsecurity默认的退出登录的路径为 /logout
<a href="/logout">退出登录</a>
</body>
</html>
3.自定义错误页面error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
自定义失败页面
</body>
</html>
其余的页面仅仅用来做跳转,这里就不详细介绍了。
4、启动类
如果想要使用角色,则需要在springboot的启动类中开启角色注解
@SpringBootApplication
@MapperScan("cn.njust.dao")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) //开启springsecurity中的@Secured角色注解,以及@PreAuthorize表达式进行角色管理。角色允许以ROLE_开头,当然也可以不加ROLE_
public class SpringbootsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootsecurityApplication.class, args);
}
}
prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解。从名字就可以看出@PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解会在方法执行后进行验证。
@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。
5、Controller层
@Controller
@RequestMapping("/admin")
public class LoginController {
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/logout")
public String logout(){
return "login";
}
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping("/error")
public String error(){
return "error";
}
@RequestMapping("/admin")
public String admin(){
return "admin";
}
//注意:这个注解一般写在controller层,注解中必须要有ROLE_
//@Secured("ROLE_syw") //角色注解,只有具有syw角色的才能访问该地址,相当于配置文件中添加.antMatchers("/admin/root").hasRole("syw")
@PreAuthorize("hasRole('syw')") //在方法执行之前进行判断,这里面放的是access权限表达式,这个hasRole里面可以写成ROLE_syw或者直接写syw,都可以
//@PostAuthorize("hasRole('syw')") //在方法执行之后进行判断,不常用。
@RequestMapping("/root")
public String root(){
return "root";
}
@RequestMapping("/normal")
public String normal(){
return "normal";
}
}
controller层可以通过注解的方式设置对应链接请求的权限,
@Secured("ROLE_syw") //角色注解,只有具有syw角色的才能访问该地址,相当于在springsecurity中添加.antMatchers("/admin/root").hasRole("syw")。
@PreAuthorize() //该注解和上面一个注解的功能没什么区别,该注解是在方法执行前进行权限校验的。
@PostAuthorize() //该注解是在方法执行之后进行判断的,不常用。
6、Service层
注意:在Service层中需要实现springsecurity默认的方法,一般是在用户操作的service方法进行实现即可,因为权限校验验证的是用户,和其他的业务无关。
//这个抽象类是springsecurity默认写好的,我们只需要重写里面的方法即可
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("我经过了loadUserByUsername");
UsersExample usersExample = new UsersExample();
UsersExample.Criteria criteria = usersExample.createCriteria();
criteria.andUsernameEqualTo(username);
List<Users> users = usersMapper.selectByExample(usersExample);
if(users==null||users.size()==0){
throw new UsernameNotFoundException("用户名不存在");
}
System.out.println(users);
//注意角色一定要以ROLE_ 开头
//给该登录成功的用户加上权限和角色,这里是自己定义的三种权限类型,root ,admin ,normal ,这里的权限要和MySecurityConfig中的hasAuthority()中的对应上
List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList("root", "admin", "normal","ROLE_syw","/update","/delete");
//AuthorityUtils.commaSeparatedStringToAuthorityList("root,admin,normal,ROLE_syw")也可以,这个可以连一块写,用逗号分割,上面那个是分开写
return new User(username,passwordEncoder.encode(users.get(0).getPassword()),authorityList);
}
为了方便演示,这里我是手动添加的权限,正常情况下权限应该是存储在数据库中,可以参照shiro权限的内容。
7、配置类
一:自定义权限异常信息处理类
//自定义处理异常信息
@Component //注入到sring中,方便下面使用
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//设置响应状态码,这里以403为例
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//设置响应的请求头
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
//这里直接打印
PrintWriter writer = httpServletResponse.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}"); //这里双引号前需要加上\进行转译
writer.flush();
writer.close();
}
}
注意,要在需要自定注入(@Autowired)到其他类中的配置类上添加@Component,例如该类,因为之后的springsecurity配置类需要使用到该类,所以这里要添加上@Component注解。
二:成功和失败的跳转链接
注意:这里可以重写成功和失败的跳转链接,但是如果不重写的话,只能跳转到本项目的链接,无法访问外部的项目。
1:自定义成功的跳转链接
//成功的跳转连接
//默认使用的是请求转发,不能访问外部的地址,无法解决前后端分离的项目,这里重写该方法,使用重定向的方式处理url,就可以跳转到外部了
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//这里也可以打印用户的信息,注意这里的user指的是 springsecurity中的user
User user = (User)authentication.getPrincipal();
System.out.println(user.getUsername());
System.out.println(user.getPassword()); //注意,为了安全,这里密码会被打印成null,并不会真正打印出来
System.out.println(user.getAuthorities()); //打印权限
httpServletResponse.sendRedirect(url);
}
}
2:自定义失败的跳转链接
//失败的跳转连接
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
三:SpringSecurity核心配置类
注意:该配置类需要添加@Configuration注解
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private DataSource dataSource;
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
//启动登录验证,所有用户的所有请求都要经过登录校验,这里如果不指定登录页面则会访问默认的登录页面
//http.formLogin().and().authorizeRequests().anyRequest().authenticated();
http.formLogin() //表单提交
.usernameParameter("user_name") //定义要接收前台登录页面的用户名属性,这里要和前台input的name属性一样
.passwordParameter("pass_word") //定义要接收的密码,要和前台中输入密码的输入框的name属性一致
.loginPage("/admin/login") //自定义登录页面
.loginProcessingUrl("/admin/login") //当发现是/admin/login时认为是登录,需要和表单提交的地址一样
.successForwardUrl("/admin/index") //登录成功的默认访问页面,仅限登录成功
//.defaultSuccessUrl("/admin/index") //登录成功的默认访问页面,和 .successForwardUrl()效果一样
//.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com")) //这里使用handler来代替上面两种方式,不能和上面两种方式同时出现,使用这个就可以跳转到外部链接了,可以用于前后端分离项目
.failureForwardUrl("/admin/error") //登录失败的页面,仅仅是登录失败,如果访问失败则会跳转到error.html,如果自己没有自定义error.html则会使用默认的,否则就会覆盖默认的,当然,如果自定义了异常处理,则会走自定义异常处理,例如我们定义的MyAccessDeniedHandler
//.failureHandler(new MyAuthenticationFailureHandler("http://www.sywblog.xyz")) //和上面的类似,注意重写的handler和failureForwardUrl不能同时出现 ,注意这里仅限于登录失败才会跳转到这个地方
.and()
.authorizeRequests()//授权认证,可以和上面分开,单独再开始一个http.authorizeRequests(),这里是写一块了
.antMatchers("/admin/login").permitAll()//访问登录页面不需要被认证, ?表示匹配一个字符,*表示匹配0个或多个字符,**表示匹配0个或多个目录,注意:这里也可以加上允许被访问的请求方式,例如.antMatchers(HttpMethod.POST,"/admin/login").permitAll(),表示只允许post方式
.antMatchers("/admin/error").permitAll()//失败页面不需要被认证,如果不放开这个,则登录失败之后还会跳转到登录页面,因为上面开放了登录页面,只有登录页面不需要认证
.antMatchers("/js/**","/css/**","/img/**").permitAll() //静态资源放行,也可以根据后缀名放行,例如: /**/*.js ,/**表示所有目录,*.js表示js文件,这放行所有目录下的js文件,上述放行之后可以直接访问http://localhost/img/canvas.png 找到我们的图片
//.regexMatchers(".+[.]png").permitAll() //使用正则表达式,当然这里也同样可以加上请求的方式
//.antMatchers("/admin/admin").hasAuthority("admin") // 这里表示只有带有admin权限才可以访问该地址,这个方法只能指定一个权限
//.antMatchers("/admin/normal").hasAnyAuthority("admin","root","normal") //这里表示具有所列出权限中的任意一种权限就可以访问
//.antMatchers("/admin/root").hasRole("syw") //这里表示带有ROLE_syw角色的才能访问,要和MyUserDetailsService中的角色对应上,这里不用加ROLE_了,如果加上启动会直接报错
//.antMatchers("/admin/root").hasAnyRole("syw,lmy") //这里表示带有ROLE_syw或者ROLE_lmy角色中的任何一个才能访问
//.antMatchers("/admin/root").hasIpAddress("192.168.1.27") //这里表示对应的ip才能访问
.anyRequest().authenticated(); //所有请求都必须授权认证之后访问
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);//异常处理
http.rememberMe()//记住我,默认失效时间为两周
//rememberMeParameter() //定义前台接收的name属性的值,默认为remember-me
.userDetailsService(userDetailsService) //自定义登录业务逻辑
.tokenRepository(persistentTokenRepository) //持久层对象,存储记住我的用户信息
.tokenValiditySeconds(30); //设置失效时间为30秒,注意需要关闭浏览器重新打开才能验证,因为浏览器不关闭就表示一次会话还没有结束
http.logout()
//.logoutUrl("/admin/logout") //默认为/logout,自定义退出登录的路径,这个地方修改完之后要与退出的超链接对应,我们一般不去修改这个。
.logoutSuccessUrl("/admin/login"); //自定义退出登录的跳转路径
http.csrf().disable(); //关闭csrf防护
http.headers().frameOptions().sameOrigin();
}
@Bean
public PasswordEncoder passwordEncoder(){
//为了安全起见,对密码进行加密
return new BCryptPasswordEncoder();
}
//实例化持久层对象,用来实现记住我功能中,存储用户的登录信息
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource); //连接数据库
//jdbcTokenRepository.setCreateTableOnStartup(true); //自动建表名为 persistent_logins用来存储登录信息,第一次启动时候需要,第二次启动需要注释掉,不然会报错,如果第一次不加入这个,则退出登录功能会失败
return jdbcTokenRepository;
}
}
到此就结束了。
8、Shiro和SpringSecurity
Shiro
Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
执行流程
特点
1.易于理解的 Java Security API;
2.简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
3.对角色的简单的签权(访问控制),支持细粒度的签权;
4.支持一级缓存,以提升应用程序的性能;
5.内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
6.异构客户端会话访问;
7.非常简单的加密 API;
8.不跟任何的框架或者容器捆绑,可以独立运行。
Spring Security
Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization)。Spring Security在架构上将认证与授权分离,并提供了扩展点。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。
执行流程
1.客户端发起一个请求,进入 Security 过滤器链。
2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。推荐:Java面试练题宝典
特点
shiro能实现的,Spring Security 基本都能实现,依赖于Spring体系,但是好处是Spring全家桶的亲儿子,集成上更加契合,在使用上,比shiro略负责。
两者对比
1.Shiro比Spring Security更容易使用,也就是实现上简单一些,同时基本的授权认证Shiro也基本够用
2.Spring Security社区支持度更高,Spring社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。
3.Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行。
我的看法
如果开发的项目是Spring这一套,用Spring Security我觉得更合适一些,他们本身就是一套东西,顺畅,可能略微复杂一些,但是学会了就是自己的。如果开发项目比较紧张,Shiro可能更合适,容易上手,也足够用,Spring Security中有的,Shiro也基本都有,没有的部分网上也有大批的解决方案。
如果项目没有使用Spring这一套,不用考虑,直接Shiro。
全部评论