市面上有两种常见的安全框架,Shiro和SpringSecurity。功能都很强大,用户数量也都很多。SpringSecurity优势在于能和Spring无缝衔接。
Shiro系列教程:https://blog.csdn.net/j080624/article/category/7006814
Shiro官网地址:https://shiro.apache.org/
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。
应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。
“认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。
“授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。
这个概念是通用的而不只在Spring Security中。
使用注解@EnableWebSecurity开启WebSecurity模式。
该注解源码如下:
/**
* Add this annotation to an {@code @Configuration} class to have the Spring Security
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending
* the {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
*在配置类上添加该注解使其获得Spring Security配置,该配置被定义在任何
WebSecurityConfigurer中或继承自WebSecurityConfigurerAdapter并重写方法的
类中,后者如下所示:
*
* @Configuration
* @EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
*
* @Override
* public void configure(WebSecurity web) throws Exception {
* web.ignoring()
* // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers("/resources/**");
* }
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest()
* .hasRole("USER").and()
* // Possibly more configuration ...
* .formLogin() // enable form based log in
* // set permitAll for all URLs associated with Form Login
* .permitAll();
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named "user" and "admin"
* .inMemoryAuthentication().withUser("user").password("password").roles("USER")
* .and().withUser("admin").password("password").roles("USER", "ADMIN");
* }
*
* // Possibly more overridden methods ...
* }
*
*
* @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication//开启全局权限配置
@Configuration// 这里引用了@Configuration该注解
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
两个重要的类:
WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
① 基础环境
默认欢迎页面如下(未登录状态),不同权限可以查看不同武功秘籍:
② 引入SpringSecurity
Starter如下:
org.springframework.boot
spring-boot-starter-security
自定义Security配置类初始如下:
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 如下,定制请求的授权规则
http.authorizeRequests()
.antMatchers("/css/**", "/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3")
}
}
此时再访问任意武林秘籍,则会提示访问被拒绝如下图:
③ 开启自动配置的登录功能
**http.formLogin();**方法会自动创建默认登录页。默认/login请求就会来到登录页,如果登录失败则会重定向到/login?error
。也可以自定义登录页,查看HttpSecurity.formLogin()
源码示例如下:
/**
* Specifies to support form based authentication.
* //指定支持基于表单的身份验证。
* If
* {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page
* will be generated.
*// 如果登录页没有指定,则会创建一个默认登录页
* Example Configurations
*
* The most basic configuration defaults to automatically generating a login page at
* the URL "/login", redirecting to "/login?error" for authentication failure. The
* details of the login page can be found on
* {@link FormLoginConfigurer#loginPage(String)}
*
*
* @Configuration
* @EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
* }
* }
*
*
* The configuration below demonstrates customizing the defaults.
*
*
* @Configuration
* @EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*// 着重观察如下配置示例 !!!
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
* }
* }
*
*
* @see FormLoginConfigurer#loginPage(String)
*
* @return
* @throws Exception
*/
public FormLoginConfigurer formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer());
}
此时还配置任何用户,没有登录没有权限,访问Level1,会跳向默认的登录页:
④ 为系统添加用户、密码和角色
配置类中添加方法:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("VIP1","VIP2")
.and()
.withUser("lisi").password("123456").roles("VIP3","VIP2")
.and()
.withUser("wangwu").password("123456").roles("VIP3","VIP1");
}
该种方法是将用户保存在内存中,项目中应该使用数据库,如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username,password, enabled from users where username = ?")
.authoritiesByUsernameQuery("select username, role from user_roles where username = ?");
}
此时不同用户就拥有了不同权限,如zhangsan只能访问普通和高级武功秘籍,访问绝世武功秘籍就会提示访问被拒绝:
⑤ 开启自动注销功能http.formLogout()
看源码注释:
/**
* Provides logout support. This is automatically applied when using
* {@link WebSecurityConfigurerAdapter}.
* // 提供了注销支持。这种能力是自动被应用的当使用了WebSecurityConfigurerAdapter。
* The default is that accessing the URL
* "/logout" will log the user out by invalidating the HTTP Session, cleaning up any
* {@link #rememberMe()} authentication that was configured, clearing the
* {@link SecurityContextHolder}, and then redirect to "/login?success".
*// 默认应用于/logout请求,删除会话session,清除rememberMe权限配置并重定向到/login?success请求。
* Example Custom Configuration
*
* The following customization to log out when the URL "/custom-logout" is invoked.
* Log out will remove the cookie named "remove", not invalidate the HttpSession,
* clear the SecurityContextHolder, and upon completion redirect to "/logout-success".
*
*
* @Configuration
* @EnableWebSecurity
* public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* .and()
* // sample logout customization
* .logout().deleteCookies("remove").invalidateHttpSession(false)
* .logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success");
* }
*
* @Override
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
* }
* }
*
*
* @return
* @throws Exception
*/
public LogoutConfigurer logout() throws Exception {
return getOrApply(new LogoutConfigurer());
}
修改请求访问规则配置方法如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
http.authorizeRequests()
.antMatchers("/css/**", "/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
// 开启自动配置的登录功能
http.formLogin();
// 开启自动配置的注销功能
http.logout();
}
在欢迎页面添加注销表单:
点击注销效果如下(重定向到了/login?logout):
修改默认退出重定向页面:
// 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
http.logout().logoutSuccessUrl("/");
此时退出返回到主页面!
需要引入thymeleaf-extras-springsecurity4,修改默认引入的Thymeleaf版本:
UTF-8
UTF-8
1.8
3.0.9.RELEASE
2.3.0
3.0.2.RELEASE
dependency>
org.thymeleaf.extras
thymeleaf-extras-springsecurity
页面引入security的名称空间:
① 测试一,修改默认访问页面提示
修改默认访问页面如下:
Insert title here
欢迎光临武林秘籍管理系统
游客您好,如果想查看武林秘籍 请登录
,您好,您的角色有:
没有认证时访问首页如下:
登录成功后,首页显示如下:
② 测试二,不同权限显示不同武林秘籍
修改页面如下:
Insert title here
欢迎光临武林秘籍管理系统
游客您好,如果想查看武林秘籍 请登录
,您好,您的角色有:
没有登录时,显示效果如下:
登录后显示效果如下:
只显示拥有的权限目录!
配置启用remember-me功能
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
http.authorizeRequests()
.antMatchers("/css/**", "/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
// 开启自动配置的登录功能
http.formLogin();
// 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
http.logout().logoutSuccessUrl("/");
//开启自动配置的Remember me
http.rememberMe();
}
此时登录页显示如下:
点击登录,查看network :
此时关闭浏览器,再次打开,直接进入登录后的页面:
① 自定义登录页面
修改配置如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
http.authorizeRequests()
.antMatchers("/css/**", "/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
// 开启自动配置的登录功能
http.formLogin().loginPage("/userlogin");
// 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
http.logout().logoutSuccessUrl("/");
//开启自动配置的Remember me
http.rememberMe();
}
修改自定义登录页面如下:
Insert title here
欢迎登陆武林秘籍管理系统
访问受保护的页面或者点击登录时,会来到我们自定义的登录页面:
FormLoginConfigurer.loginPage方法源码与注释如下:
/**
*
* Specifies the URL to send users to if login is required.
* 发送给用户的登录请求
* If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be generated when
* this attribute is not specified.
* 如果没有被指定则使用默认的登录页面
*
*
*
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the specified
* URL to generate a login page.
* 如果url被指定或没有使用默认的登录页面,用户需要处理登录请求跳转到登录页面
* In general, the login page should create a form that
* submits a request with the following requirements to work with
* {@link UsernamePasswordAuthenticationFilter}:
* // 一般来说,登录页面需要创建一个表单提交如下所需的参数属性:
*
*
*
* - It must be an HTTP POST
* - It must be submitted to {@link #loginProcessingUrl(String)}
* - It should include the username as an HTTP parameter by the name of
* {@link #usernameParameter(String)}
* - It should include the password as an HTTP parameter by the name of
* {@link #passwordParameter(String)}
*
*
* Example login.jsp
*
* Login pages can be rendered with any technology you choose so long as the rules
* above are followed. Below is an example login.jsp that can be used as a quick start
* when using JSP's or as a baseline to translate into another view technology.
*下面是一个JSP例子,可以被用来快速创建登录页。
*
*
* <c:url value="/login" var="loginProcessingUrl"/>
* <form action="${loginProcessingUrl}" method="post">
* <fieldset>
* <legend>Please Login</legend>
* <!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error -->
* <c:if test="${param.error != null}">
* <div>
* Failed to login.
* <c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}">
* Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
* </c:if>
* </div>
* </c:if>
* <!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout -->
* <c:if test="${param.logout != null}">
* <div>
* You have been logged out.
* </div>
* </c:if>
* <p>
* <label for="username">Username</label>
* <input type="text" id="username" name="username"/>
* </p>
* <p>
* <label for="password">Password</label>
* <input type="password" id="password" name="password"/>
* </p>
* <!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter -->
* <p>
* <label for="remember-me">Remember Me?</label>
* <input type="checkbox" id="remember-me" name="remember-me"/>
* </p>
* <div>
* <button type="submit" class="btn">Log in</button>
* </div>
* </fieldset>
* </form>
*
*
* Impact on other defaults
*
* Updating this value, also impacts a number of other default values.
* // 更新默认值,将会影响其他默认值
* For example,
* the following are the default values when only formLogin() was specified.
*// 下面是当formLogin被开启时一系列默认值
*
* - /login GET - the login form
* - /login POST - process the credentials and if valid authenticate the user
* - /login?error GET - redirect here for failed authentication attempts
* - /login?logout GET - redirect here after successfully logging out
*
*
* If "/authenticate" was passed to this method it update the defaults as shown below:
*
*
* - /authenticate GET - the login form
* - /authenticate POST - process the credentials and if valid authenticate the user
*
* - /authenticate?error GET - redirect here for failed authentication attempts
* - /authenticate?logout GET - redirect here after successfully logging out
*
*
*
* @param loginPage the login page to redirect to if authentication is required (i.e.
* "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
@Override
public FormLoginConfigurer loginPage(String loginPage) {
return super.loginPage(loginPage);
}
通过阅读上述源码,我们尝试修改用户名、密码默认参数名如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
http.authorizeRequests()
.antMatchers("/css/**", "/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
// 开启自动配置的登录功能
http.formLogin().usernameParameter("name").passwordParameter("pwd").loginPage("/userlogin");
// 开启自动配置的注销功能,默认重定向到/logout?success,修改为"/"
http.logout().logoutSuccessUrl("/");
//开启自动配置的Remember me
http.rememberMe();
}
Insert title here
欢迎登陆武林秘籍管理系统
再次测试登录:
② 为自定义登录页面添加Remember-me
首先查看使用默认登录页面时remember-me的源码:
可以看到,添加了一个复选框,name为"remember-me"。
同样,在我们的登录表单中,也添加该复选框即可!
修改表单如下:
登录测试如下图:
同样可以查看RememberMeConfigurer源码获取记住我的默认name:
public final class RememberMeConfigurer>
extends AbstractHttpConfigurer, H> {
/**
* The default name for remember me parameter name and remember me cookie name
*/
private static final String DEFAULT_REMEMBER_ME_NAME = "remember-me";
private AuthenticationSuccessHandler authenticationSuccessHandler;
private String key;
private RememberMeServices rememberMeServices;
private LogoutHandler logoutHandler;
private String rememberMeParameter = DEFAULT_REMEMBER_ME_NAME;
private String rememberMeCookieName = DEFAULT_REMEMBER_ME_NAME;
private String rememberMeCookieDomain;
private PersistentTokenRepository tokenRepository;
private UserDetailsService userDetailsService;
private Integer tokenValiditySeconds;
private Boolean useSecureCookie;
private Boolean alwaysRemember;
//...
}
也可以使用自定义记住我name:
//开启自动配置的Remember me,并修改记住我的参数name
http.rememberMe().rememberMeParameter("remember");
CSRF(Cross-site request forgery)跨站请求伪造。
HttpSecurity默认启用csrf功能,会为表单添加_csrf的值,提交携带来预防CSRF。
如点击登录,查看network:
本篇博文项目地址:https://github.com/JanusJ/SpringBoot/tree/master/security