-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> -
实现自定义逻辑
-
编写一个类实现UserDetailsService接口
-
重写loadUserByUsername方法
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ 根据用户名从数据库查询用户对象 根据用户ID从数据库查询用户角色与用户权限 User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList(“”)) // 在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称 } 返回值:UserDetails的实现类 org.springframework.security.core.userdetails.User 参数:客户端表单传递过来的数据,必须叫username 异常:UsernameNotFoundException 用户名没有发现异常
-
创建配置类
@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { /* * forLogin()表示手动配置认证逻辑 * loginPage()设置登录页面是什么 * successForwardUrl()设置登录请求逻辑 * successForwardUrl()设置认证成功后主动跳转的控制器路径 * 默认是上一个页面请求什么,认证成功后跳转那个页面 * 如果是直接登录认证,成功后访问: index.jsp/index.html * 如果设置successForwardUrl(), 则认证成功后跳转该请求的控制器 * failureForwardUrl()设置认证失败后主动跳转的控制器路径 * usernameParameter("uname") 修改username的请求参数 * passwordParameter("pwd") 修改password的请求参数 * */ http.formLogin() .loginProcessingUrl("/login") .successForwardUrl("/main") .failureForwardUrl("/fail") //.usernameParameter("uname") //.passwordParameter("pwd") .loginPage("/login.html"); /** * authorizeRequests()设置请求权限 * antMatchers("/login.html").permitAll() * 表示login.html所有请求都可以访问,不需要被认证 * antMatchers("/login.html").anonymous() * 表示login.html路径可以匿名访问 * anyRequest().authenticated() 相当于 antMatchers("/**") 表示所有请求必须先认证 */ http.authorizeRequests() .antMatchers("/login.html").permitAll() .antMatchers("/fail.html").permitAll() .antMatchers("/js/**","/css/**").permitAll() .anyRequest().authenticated(); // 关闭csrf保护 http.csrf().disable(); } /** * 密码凭证器 * @return BCryptPasswordEncoder */ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
-
内置控制方法介绍
* permitAll() 表示所匹配的URL任何人都允许访问 * authentication() 表示所匹配的URL都需要被认证才能访问 * anonymous() 表示可以匿名访问匹配的URL * denyAll() 表示所匹配的URL都不允许被访问 * rememberMe() 被“remember me”的用户允许访问 * fullyAuthentication() 如果用户不是被remember me的,才可以访问 -
角色权限判断
* hasAuthority(String) * 判断用户是否具有特定的权限 * .antMatchers("/user.html").hasAuthority("/user") * hasAnyAuthority(String) * 如果用户具备给定权限中某一个,就允许访问 * .antMatchers("/product.html").hasAnyAuthority("/product", "/order") * hasRole(String) * 如果用户具备给定角色就允许访问。否则出现403。 * 在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_abc 其中abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。 * .antMatchers("/system.html").hasRole("管理员") * hasAnyRole(String ...) * 如果用户具备给定角色的任意一个,就允许被访问 * hasIpAddress(String) * 如果请求是指定的IP就运行访问。 -
基于注解的访问控制
* 在启动类(也可以在配置类等能够扫描的类上)上添加 @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) * 在控制器的方法上添加@Secured注解 @Secured("ROLE_abc") @RequestMapping("/toMain") public String toMain(){ return "redirect:/main.html"; } * @PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解 @PreAuthorize("hasRole('abc')") @RequestMapping("/toMain") public String toMain(){ return "redirect:/main.html"; } * @PostAuthorize表示方法或类执行结束后判断权限,此注解很少被使用到。 -
Remember Me功能实现
* Spring Security会自动把用户信息存储到数据源中,以后就可以不登录进行访问 * 编写配置类 @Configuration public class RememberMeConfig { @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl(); jdbcTokenRepositoryImpl.setDataSource(dataSource); //自动建表,第一次启动时需要,第二次启动时注释掉 // jdbcTokenRepositoryImpl.setCreateTableOnStartup(true); return jdbcTokenRepositoryImpl; } } * 修改SecurityConfig * 注入 private PersistentTokenRepository persistentTokenRepository; * 注入 private UserDetailsService userDetailsService; http.rememberMe() .tokenValiditySeconds(120)//单位:秒 , 有效时间,默认2周 .userDetailsService(userDetailsService) //登录逻辑交给哪个对象 .tokenRepository(persistentTokenRepository); //持久层对象 ``` * 在客户端页面中添加复选框 <input type="checkbox" name="remember-me" value="true"/>
hymeleaf中Spring Security的使用
-
在html页面中引用thymeleaf命名空间和security命名空间
在html页面中引用thymeleaf命名空间和security命名空间 -
name:登录账号名称
登录账号:<span sec:authentication="name"></span> -
principle : 登录主体 , 在自定义登录逻辑中是UserDetails
登录账号:<span sec:authentication="principal.username"></span> -
credentials : 凭证
凭证:<span sec:authentication="credentials"></span> -
authorities : 权限和角色
权限和角色:<span sec:authentication="authorities"></span> -
details : 实际上是WebAuthenDetails的实例 , 可以获取remoteAddress(客户端ip)和sessionId(当前sessionId)
客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/> sessionId:<span sec:authentication="details.sessionId">456</span><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/> 通过角色判断: <button sec:authorize="hasRole('abc')">新增</button> <button sec:authorize="hasRole('abc')">删除</button> <button sec:authorize="hasRole('abc')">修改</button> <button sec:authorize="hasRole('abc')">查看</button>
退出登录
-
在页面中添加/logout的超链接即可
<a href="/logout">退出登录</a> -
修改SecurityConfig
http.logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html");
Spring Security中CSRF
-
CSRF(Cross-site request forgery)跨站请求伪造
-
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
-
在跨域的情况下,session id可能被第三方恶意劫持(session id是 cookie中存放用来识别客户端身份的)
-
通过伪造用户请求访问受信任站点的非法请求访问
-
-
从Spring Security4开始CSRF防护默认开启。默认会拦截请求。
-
CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。
-
开启csrf防护情况下,修改登录页面
<form action = "/login" method="post"> <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/> 用户名:<input type="text" name="username"/><br/> 密码:<input type="password" name="password"/><br/> <input type="submit" value="登录"/> </form> -
如果我们开启了CSRF保护机制,则默认情况下,不能使用get方式的/logout
-
使用form表单退出
<form style="float: right" action="/logout" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <button type='submit'>退出</button> </form>