当前两大权限认证框架:shiro和Spring Security
SpringBoot版本:1.5.8
1.在SpringBoot中欲使用Spring Security,首先需要添加依赖:
<!--声明使用Spring security的依赖-->
<
dependency
>
<
groupId
>
org.springframework.boot
</
groupId
>
<
artifactId
>
spring-boot-starter-security
</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>
org.thymeleaf.extras
</
groupId
>
<
artifactId
>
thymeleaf-extras-springsecurity4
</
artifactId
>
</
dependency
>
2.在Application的Properties文件中,配置:
#Spring Security config
logging.level.org.springframework.security
=
info
3.写一个Security的Java配置类:
package
com.kgoos.app.configure;
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.WebSecurityConfigurerAdapter;
import
org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
public class
WebSecurityConfig
extends
WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
//2
return new
SysUserServiceImpl();
}
@Override
protected void
configure(AuthenticationManagerBuilder auth)
throws
Exception {
auth.userDetailsService(customUserService());
}
@Override
protected void
configure(HttpSecurity http)
throws
Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(
"/login"
)
.failureUrl(
"/login?error"
)
.permitAll()
.and()
.logout().permitAll();
}
}
在SpringMVC中也添加配置:
@Configuration
public class
MyWebConfig
extends
WebMvcConfigurerAdapter {
@Override
public void
addViewControllers(ViewControllerRegistry registry) {
registry.addViewController(
"/login"
).setViewName(
"login"
);
}
}
4.准备SpringSecurity认证需要的两个实体类:
SysRole和
SysUser
import
lombok.
Getter
;
import
lombok.
Setter
;
import
javax.persistence.
Entity
;
import
javax.persistence.
GeneratedValue
;
import
javax.persistence.
Id
;
import
java.io.Serializable;
/**
* Description:This PO is used for system auth :Spring Security
*
@author
dbdu
*
@date
17-12-21 上午9:34
*/
@Entity
@Getter@Setter
public class
SysRole
implements
Serializable { //注意此处要实现序列化接口,否则终端执行会出错!
@Id
@GeneratedValue
private
Long
id
;
private
String
name
;
}
------------------------------------------
import
java.util.ArrayList;
import
java.util.Collection;
import
java.util.List;
import
javax.persistence.CascadeType;
import
javax.persistence.
Entity
;
import
javax.persistence.FetchType;
import
javax.persistence.
GeneratedValue
;
import
javax.persistence.
Id
;
import
javax.persistence.
ManyToMany
;
import
lombok.
Getter
;
import
lombok.
Setter
;
import
org.springframework.security.core.GrantedAuthority;
import
org.springframework.security.core.authority.SimpleGrantedAuthority;
import
org.springframework.security.core.userdetails.UserDetails;
@Entity
@Getter@Setter
public class
SysUser
implements
UserDetails {
//1
private static final long
serialVersionUID
=
1L
;
@Id
@GeneratedValue
private
Long
id
;
private
String
username
;
private
String
password
; //当然若是实际项目中使用,可能会有其他更多的属性.
@ManyToMany
(cascade = {CascadeType.
REFRESH
}, fetch = FetchType.
EAGER
)
private
List<SysRole>
roles
;
@Override
public
Collection<?
extends
GrantedAuthority> getAuthorities() {
//2
List<GrantedAuthority> auths =
new
ArrayList<GrantedAuthority>();
List<SysRole> roles =
this
.getRoles();
for
(SysRole role : roles) {
auths.add(
new
SimpleGrantedAuthority(role.getName()));
}
return
auths;
}
@Override
public boolean
isAccountNonExpired() {
return true
;
}
@Override
public boolean
isAccountNonLocked() {
return true
;
}
@Override
public boolean
isCredentialsNonExpired() {
return true
;
}
@Override
public boolean
isEnabled() {
return true
;
}
}
5.准备实体对应的repository和Service
import
com.kgoos.app.entity.SysUser;
import
org.springframework.data.jpa.repository.JpaRepository;
public interface
SysUserRepository
extends
JpaRepository<SysUser, Long> {
SysUser findByUsername(String username);
}
---------------------------------------
import
com.kgoos.app.entity.SysUser;
import
com.kgoos.app.repository.SysUserRepository;
import
org.springframework.beans.factory.annotation.
Autowired
;
import
org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.core.userdetails.UsernameNotFoundException;
@Service
public class
SysUserServiceImpl
implements
ISysUserService {
//1
@Autowired
SysUserRepository
userRepository
;
@Override
public
UserDetails loadUserByUsername(String username) {
//2
SysUser user =
userRepository
.findByUsername(username);
if
(user ==
null
){
throw new
UsernameNotFoundException(
"用户名不存在"
);
}
return
user;
//3
}
}
/**
* Description:此处继承仅是为了让代码看起来更加规范而已
* Created at:2017-12-21 10:12,
* by dbdu
*/
public interface
ISysUserService
extends
UserDetailsService {}
6.向数据表中插入基本的数据:
insert into SYS_USER (id,username, password) values (1,'dbdu', 'dbdu');
insert into SYS_USER (id,username, password) values (2,'dusuanyun', 'dusuanyun');
insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
insert into SYS_USER_ROLES(SYS_USER_ID,ROLES_ID) values(1,1);
insert into SYS_USER_ROLES(SYS_USER_ID,ROLES_ID) values(2,2);
7.注意login和logout的url是固定的,为/login和/logout,不需要专门为这两个url写控制器!!
8.准备login.html和home.html的thymeleaf文件放置在,resources--templates目录下.
9.详细信息,参考KGoos项目的security_test_branch分支,此分支配置了此框架的认证方式.
疑问1:如何根据角色不同,区分什么是admin的,什么是普通用户的?
解答:
方式一:使用thymeleaf有方式可以判断角色
A.先引入命名空间:
<
html
xmlns:
th
=
"http://www.thymeleaf.org"
xmlns:
sec
=
"http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
>
B.使用
hasRole('ROLE_ADMIN')"
来判断角色:
<
div
sec
:authorize=
"hasRole('ROLE_ADMIN')"
>
<
p
class=
"bg-info"
th
:text=
"${msg.etraInfo}"
></
p
>
</
div
>
方式二:使用异步Ajax的方式
如果前端需要角色信息来决定显示什么页面,可以考虑提供控制器将用户信息以Json的方式给前端!
疑问2:如何添加用户?
解决:利用Hibernate的映射关系存储关联关系!
疑问3:如何使用密码的密文? ---现在存储的是明文
解决:
第一步:自己定义一个密码编码解码的类实现
PasswordEncoder
接口
/**
* Description: 这个类是专为使用SpringSecurity认证框架设计的,用户处理密码是明码还是密码的问题!
* Created at:2017-12-21 14:18,
* by dbdu
*/
public class
MD5PasswordEncoder
implements
PasswordEncoder {
@Override
public
String encode(CharSequence charSequence) {
return
MD5Util.
encode
((String) charSequence);
}
@Override
public boolean
matches(CharSequence rawPassword, String encodedPassword) {
if
(
null
!= encodedPassword && encodedPassword.equals(encode(rawPassword))) {
return true
;
}
return false
;
}
}
第二步:在
WebSecurityConfig类中,增加自定义的编码解码器,例如:
@Configuration
public class
WebSecurityConfig
extends
WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
//2
return new
SysUserServiceImpl();
}
@Override
protected void
configure(AuthenticationManagerBuilder auth)
throws
Exception {
//指定密码的编码和解码方式:MD5PasswordEncoder
auth.userDetailsService(customUserService())
.passwordEncoder(new MD5PasswordEncoder());
//auth.userDetailsService(sysUserService);
}
@Override
protected void
configure(HttpSecurity http)
throws
Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(
"/login"
)
.failureUrl(
"/login?error"
)
.permitAll()
.and()
.logout().permitAll();
}
}
疑问4:如何将登录的用户,用户名显示在页面上,其实就是如何获取已经登录成功的用户的信息
解决:没有测试过
A.JSP的方式:
principal 用户的基本信息对象
B.Thymeleaf的方式:
方式一:使用表达式实用程序对象
该
#authentication
对象可以很容易地使用,就像这样:
<
div
th:text
=
“ $ {#authentication.name} ”
> 认证对象的“name”属性的值应该出现在这里。 </
div
>
方式二:使用
sec:authentication
属性相当于使用
#authentication
对象,但使用自己的属性:
<
div
sec:authentication
=
“ name ”
> 认证对象的“name”属性的值应该出现在这里。 </
div
>
C.自己的思路:
登录成功后,发请求,然后将请求到的数据直接存到session会话里,写一个监听器session注销的时候,清除保存在session里的数据,当然你要是不管它好像也没什么问题.
参考资料:
A.<<JavaEE开发的颠覆者-SpringBoot实战>>,第九章第一节
B.<<Spring实战第四版中文版>>,第九章